I'm trying to open the CSV file downloaded using DownloadManager using the apps available in the phone. But I am not able to open it. The third party apps returns giving error Toast messages or Alert Dialog saying file location not found. But when, I open file manually, the app is getting opened normally.
I have written below code for accessing the downloaded file and opening using Intent Chooser.
fun openDownloadedFile(referenceId : Long){
if (downloadReference === referenceId)
{
val query = DownloadManager.Query()
query.setFilterById(downloadReference)
var downloadManager = activity?.getSystemService(DOWNLOAD_SERVICE) as DownloadManager?
val c = downloadManager?.query(query)!!
if (c.moveToFirst())
{
val columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS)
if (DownloadManager.STATUS_SUCCESSFUL === c.getInt(columnIndex))
{
var localUri:String = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
if (localUri.substring(0, 7).equals("file://")) {
localUri = localUri.substring(7);
}
var file = File(localUri);
val fileExtension = MimeTypeMap.getFileExtensionFromUrl(localUri)
var mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension)
if (mimeType != null)
{
mimeType = "text/csv" // even after removing manual mime type it is not working.
var intent = Intent(Intent.ACTION_VIEW);
//var file = File(localUri);
var absoluteFilePath = file.getAbsolutePath();
var uri = Uri.parse( "content:/"+absoluteFilePath);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.setDataAndTypeAndNormalize(uri, mimeType);
try
{
// Add chooser to open file.
var intentChooser = Intent.createChooser(intent, "Choose Application");
startActivityForResult(intentChooser,1000);
}
catch (e:ActivityNotFoundException) {
}
}
} else {
Log.d(“TAG”, "Download Failed.. status : "+c.getInt(columnIndex))
}
}
}
}
When I chose, Microsoft Excel, it shows Cant open file error.
Any help is appreciated.
Related
This question already has answers here:
exposed beyond app through ClipData.Item.getUri
(4 answers)
Closed 8 months ago.
I want to share my application using a share button inside it. Once the button is clicked it should get the base.apk from the package manager and then share it using Intents.
Here is what I have so far:
All UI is ready and working
I have the following code to get the app and share it
try {
val pm = packageManager
val ai = pm.getApplicationInfo(packageName, 0)
val srcFile = File(ai.publicSourceDir)
val share = Intent()
share.action = Intent.ACTION_SEND
share.type = "application/vnd.android.package-archive"
share.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(srcFile))
startActivity(Intent.createChooser(share, "Sharing"))
} catch (e: Exception) {
UtilityMethods(this).toast("Failed To Share The App", "e")
e.printStackTrace()
}
But I get an error with this procedure.
android.os.FileUriExposedException: file:///data/app/~~BC-clKZDViP_O7n44ooPbQ%3D%3D/MyAppPublicSourceDirectory/base.apk exposed beyond app through ClipData.Item.getUri()
Is there any help I can get regarding this? I tried a lot of solutions, but they don't work for me.
EDIT:: Updated Code, Copy the base.apk to Downloads Folder and Rename it. Then try to share it (which is where the error invokes from).
try {
// get the base apk of the app
val pm = packageManager
val ai = pm.getApplicationInfo(packageName, 0)
val srcFile = File(ai.publicSourceDir)
// save the file in Downloads folder
val dstFile = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
"LogsCalculator.apk"
)
dstFile.createNewFile()
val input = FileInputStream(srcFile)
val output = FileOutputStream(dstFile)
val buffer = ByteArray(1024)
var length: Int = input.read(buffer)
while (length > 0) {
output.write(buffer, 0, length)
length = input.read(buffer)
}
output.flush()
output.close()
input.close()
// share the apk file now
val intent = Intent(Intent.ACTION_SEND)
intent.type = "application/vnd.android.package-archive"
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(dstFile))
startActivity(Intent.createChooser(intent, getString(R.string.sharing)))
} catch (e: Exception) {
UtilityMethods(this).toast("Failed To Share The App", "e")
e.printStackTrace()
}
It Still Does Not Work.
On later API versions, not even backup tools can obtain the APK anymore, therefore the approach is literally pointless. Instead use Firebase Dynamic Links, in order to permit user to user sharing of your application. This way they can install from Google Play Store, instead up installing some APK of unknown origin, which may not update well.
I got the solution.
try {
// get the base.apk
val baseApkLocation =
applicationContext.packageManager.getApplicationInfo(
applicationContext.packageName,
PackageManager.GET_META_DATA
).sourceDir
// get the file
val baseApk = File(baseApkLocation)
// the path
val path = Environment.getExternalStorageDirectory().toString() + "/Download/"
// make the directory
val dir = File(path)
// if the directory doesn't exist, make it
if (!dir.exists()) {
dir.mkdirs()
}
// Copy the .apk file to downloads directory
val destination = File(
path + "MyAppName.apk"
)
if (destination.exists()) {
destination.delete()
}
destination.createNewFile()
val input = FileInputStream(baseApk)
val output = FileOutputStream(destination)
val buffer = ByteArray(1024)
var length: Int = input.read(buffer)
while (length > 0) {
output.write(buffer, 0, length)
length = input.read(buffer)
}
output.flush()
output.close()
input.close()
// get content uri for the file
val uri = FileProvider.getUriForFile(
this,
BuildConfig.APPLICATION_ID + ".provider",
destination
)
// share the file
val intent = Intent(Intent.ACTION_SEND)
intent.type = "application/vnd.android.package-archive"
intent.putExtra(Intent.EXTRA_STREAM, uri)
startActivity(Intent.createChooser(intent, getString(R.string.share_app)))
} catch (e: Exception) {
Lib(this).toast("Failed To Share The App", "e")
e.printStackTrace()
}
Here is how it works
Get the base.apk file of the app from its source dir.
Copy the file to the new location and give it a meaningful name.
Get the content URI of the new file. This is what was missing.
// get content uri for the file
val uri = FileProvider.getUriForFile(
this,
BuildConfig.APPLICATION_ID + ".provider",
destination
)
Share the file.
Anyways thanks for all the help.
I am calling below function to download a binary file.
fun downloadFile(
baseActivity: Context,
batteryId: String,
downloadFileUrl: String?,
title: String?
): Long {
val directory =
File(Environment.getExternalStorageDirectory().toString() + "/destination_folder")
if (!directory.exists()) {
directory.mkdirs()
}
//Getting file extension i.e. .bin, .mp4 , .jpg, .png etc..
val fileExtension = downloadFileUrl?.substring(downloadFileUrl.lastIndexOf("."))
val downloadReference: Long
var objDownloadManager: DownloadManager =
baseActivity.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val uri = Uri.parse(downloadFileUrl)
val request = DownloadManager.Request(uri)
//Firmware file name as batteryId and extension
firmwareFileSubPath = batteryId + fileExtension
request.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
"" + batteryId + fileExtension
)
request.setTitle(title)
downloadReference = objDownloadManager.enqueue(request) ?: 0
return downloadReference
}
Once the file got downloaded I am receiving it in below onReceive() method of Broadcast receiver:
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == DownloadManager.ACTION_DOWNLOAD_COMPLETE) {
intent.extras?.let {
//retrieving the file
val downloadedFileId = it.getLong(DownloadManager.EXTRA_DOWNLOAD_ID)
val downloadManager =
getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val uri: Uri = downloadManager.getUriForDownloadedFile(downloadedFileId)
viewModel.updateFirmwareFilePathToFirmwareTable(uri)
}
}
}
I am downloading the files one by one and wants to know that which file is downloaded.
Based on the particular file download, I have to update the entry in my local database.
So, here in onReceive() method how can I identify that which specific file is downloaded?
Thanks.
One way to identify your multiple downloads simultaneously is to track id returned from DownloadManager to your local db mapped to given entry when you call objDownloadManager.enqueue(request).
Document of DownloadManager.enquque indicates that:
Enqueue a new download. The download will start automatically once the download manager is ready to execute it and connectivity is available.
So, if you store that id mapped to your local database entry for given record then during onReceive() you can identify back to given record.
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == DownloadManager.ACTION_DOWNLOAD_COMPLETE) {
intent.extras?.let {
//retrieving the file
val downloadedFileId = it.getLong(DownloadManager.EXTRA_DOWNLOAD_ID)
// Find same id from db that you stored previously
val downloadManager =
getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val uri: Uri = downloadManager.getUriForDownloadedFile(downloadedFileId)
viewModel.updateFirmwareFilePathToFirmwareTable(uri)
}
}
}
Here, it.getLong(DownloadManager.EXTRA_DOWNLOAD_ID) returns you the same id for which download was started previously and enqueue returned.
Document for EXTRA_DOWNLOAD_ID indicates that:
Intent extra included with ACTION_DOWNLOAD_COMPLETE intents, indicating the ID (as a long) of the download that just completed.
You have the Uri of file, now simply get the file name to identify the file, you can use following function to get file name
fun getFileName(uri: Uri): String? {
var result: String? = null
when(uri.scheme){
"content" -> {
val cursor: Cursor? = getContentResolver().query(uri, null, null, null, null)
cursor.use {
if (it != null && it.moveToFirst()) {
result = it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME))
}
}
}
else -> {
val lastSlashIndex = uri.path?.lastIndexOf('/')
if(lastSlashIndex != null && lastSlashIndex != -1) {
result = uri.path!!.substring(lastSlashIndex + 1)
}
}
}
return result
}
So,
I would like to open the file manager from my application, with my application private storage, to show all the files.
I tried to open with Action_VIEW,ACTION_GET_CONTENT
And I also tried set intent type to /,application/*
The result is the android offer a lot off application but not the file exporer, or I just simple get error, because there is no application to the action.
I use a Samsung tablet, with default file explorer
[Updated]
val uri = Uri.parse(requireContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.path)
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.setDataAndType(uri,"resource/folder")
try {
startActivity(intent)
}catch (e: Exception){
throw e
}
Open document tree intent
fun createDocumentTreeIntent(fileName: String, extension: String): Intent {
return Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = if (extension == "jpeg" || extension == "jpg" || extension == "png")
"image/${extension}"
else
"application/${extension}"
putExtra(Intent.EXTRA_TITLE, "${fileName}.${extension}")
}
}
My current Android Application stores pdf files on external storage using
val contentUri = MediaStore.Files.getContentUri(VOLUME_NAME_EXTERNAL)
The application creates a sub folder in the standard Documents folder.
My manifest contains
android:requestLegacyExternalStorage = true
For Android 30 I request the following
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
val uri = Uri.fromParts("package", packageName, null)
intent.data = uri
startActivity(intent)
I have a background worker that attempts to clear all down loaded files, the code I employ is as follows:-
#RequiresApi(Build.VERSION_CODES.Q)
override suspend fun doActualFlowedWork(): Result {
if (hasSdkHigherThan(Build.VERSION_CODES.P)) {
clearDownloadFiles()
} else {
clearDownloadLegacyFiles()
}
return result ?: Result.success()
}
#Suppress("BlockingMethodInNonBlockingContext")
#RequiresApi(Build.VERSION_CODES.Q)
private fun clearDownloadFiles() {
val resolver = context.contentResolver
val relativeLocation = "${Environment.DIRECTORY_DOCUMENTS}${MY_SUB_FOLDER}"
val contentUri = MediaStore.Files.getContentUri(VOLUME_NAME_EXTERNAL)
resolver.query(
contentUri,
arrayOf(MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.RELATIVE_PATH),
"${MediaStore.MediaColumns.RELATIVE_PATH}=?",
arrayOf(relativeLocation),
null
).use { cursor ->
cursor?.let {
while (it.moveToNext()) {
val idIndex = cursor.getColumnIndex(MediaStore.MediaColumns._ID)
val displayNameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)
val relativePathNameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.RELATIVE_PATH)
if (cursor.getString(relativePathNameIndex) == relativeLocation) {
val fileContentUri = MediaStore.Files.getContentUri(VOLUME_NAME_EXTERNAL, cursor.getLong(idIndex))
val count = context.contentResolver.delete(fileContentUri, null, null)
if (count == 0) Timber.e("FAILED to clear downloaded file = ${cursor.getString(displayNameIndex)}")
else Timber.i("Cleared downloaded file = ${cursor.getString(displayNameIndex)}")
}
}
}
}
}
#Suppress("DEPRECATION")
private fun clearDownloadLegacyFiles() {
val documentsFolder = File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOCUMENTS)
if (!documentsFolder.exists()) return
val mendeleyLiteSubFolder = File(Environment.getExternalStorageDirectory(), "${Environment.DIRECTORY_DOCUMENTS}$MY_SUB_FOLDER")
if (!mendeleyLiteSubFolder.exists()) return
val downloadFiles = mendeleyLiteSubFolder.listFiles()
downloadFiles?.forEach { downloadFile ->
if (downloadFile.exists()) downloadFile.delete()
Timber.i("Clearing downloaded file = $downloadFile")
}
}
This clear down worker completes OK, with the logs showing the files have been deleted
however when I use Android Studio Device File Explorer to view my Document sub folder the physical pdf files are still present.
Are my expectations incorrect?
What does this code achieve context.contentResolver.delete(fileContentUri, null, null)?
How do I delete physical files from my sub folder?
My android app flow is as follows:
Creates a csv file.
Store it in the default app folder.
Generates a notification of succesful creation
Open the csv in the default app.
I can not be able to open the csv via Intent or other methods so far.
With the following code I'm able to open the explorer but not in the folder path.
var path = this.applicationContext.getExternalFilesDir(null)?.absolutePath.toString()
println("Hey my path is $path")
val csvWriter = CSVWriter(
fileWriter,
CSVWriter.DEFAULT_SEPARATOR,
CSVWriter.NO_QUOTE_CHARACTER,
CSVWriter.DEFAULT_ESCAPE_CHARACTER,
CSVWriter.DEFAULT_LINE_END
)
csvWriter.writeAll(data.toMutableList())
csvWriter.close()
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/*"
}
startActivityForResult(intent, 2)
Also, I tried to develop a function but without effective response:
fun open_file(filename: String?) {
val path = File(filesDir, "dl")
val file = File(path, filename)
// Get URI and MIME type of file
val uri = FileProvider.getUriForFile(this, "$PACKAGE_NAME.fileprovider", file)
val mime = contentResolver.getType(uri)
// Open file with user selected app
val intent = Intent()
intent.action = Intent.ACTION_VIEW
intent.setDataAndType(uri, mime)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(intent)
}
I saw some possible solutions developed in java but there are deprecated and / or does not work converted in Kotlin.
Edit:
Given the suggestion of #CommonsWare, I'm trying to save the file in the path given but the user. However, I get the error : No Activity found to handle Intent
val FILE_EXPORT_REQUEST_CODE = 12
val exportIntent = Intent(Intent.ACTION_CREATE_DOCUMENT)
exportIntent.addCategory(Intent.CATEGORY_OPENABLE)
exportIntent.type = "text/csv"
val uri = Uri.parse("$path/file.csv")
exportIntent.data = uri
val filename = "file.csv"
exportIntent.putExtra(Intent.EXTRA_TITLE, filename)
startActivityForResult(exportIntent, FILE_EXPORT_REQUEST_CODE)
Thanks in advance.