Open PDF File from Download Manager Located from Download - android

I am using DownloadManager to download a file from FirebaseStorage.
First, I will get the downloadUrl from FirebaseStorage and proceed with DownloadManager
As you can see codes below, this is where after I got the downloadUrl as url.
downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
val uri = Uri.parse(url)
val request = DownloadManager.Request(uri)
val folderName = File.separator + "MBITION" + File.separator + fileName + fileExtension
name = folderName
Log.i("???", "url: $url")
Log.i("???", "folderName: $folderName")
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
request.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
folderName
)
enq = downloadManager!!.enqueue(request)
Logcat below shows the url and folderName value.
I/???: url: https://firebasestorage.googleapis.com/v0/b/mbition-2022.appspot.com/o/note%2F-N2_fRAhJXVshDjQMcrz?alt=media&token=936e1014-6c7a-4f46-89fd-5746eb6a9dbf
I/???: folderName: /MBITION/ICT600 - Chapter 3.pdf
As you can see codes below, where onReceive from BroadcastReceiver is handled.
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE == action) {
val downloadId = it.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0)
val query = DownloadManager.Query()
query.setFilterById(downloadId)
val c: Cursor = downloadManager!!.query(query)
if (c.moveToFirst()) {
val columnIndex: Int = c.getColumnIndex(DownloadManager.COLUMN_STATUS)
if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
val uriString: String = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
val file = File(uriString)
val target = Intent(Intent.ACTION_VIEW)
Log.i("???", "uriString:: $uriString")
Log.i("???", "file:: $file")
val uri = FileProvider.getUriForFile(
this#NoteActivity,
"$packageName.provider",
file
)
target.setDataAndType(uri, "application/pdf")
target.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
val intentChooser = Intent.createChooser(target, "Open File")
try {
startActivity(intentChooser)
} catch (e: ActivityNotFoundException) {
Tools.showToast(
this#NoteActivity,
"You need to download PDF reader"
)
}
}
}
}
Below shows the Logcat.
I/???: uriString:: file:///storage/emulated/0/Download/MBITION/ICT600%20-%20Chapter%203-5.pdf
I/???: file:: file:/storage/emulated/0/Download/MBITION/ICT600%20-%20Chapter%203-5.pdf
This is my provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external_files"
path="." />
</paths>
I tried to open the file through the DownloadManager's Notification. It is working fine.
Image below shows where the file is located inside the MBITION folder.
Below is the error I got from Logcat.
2022-05-28 11:36:44.534 4999-4999/com.aaa.mbition E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.aaa.mbition, PID: 4999
java.lang.IllegalArgumentException: Failed to find configured root that contains /file:/storage/emulated/0/Download/MBITION/ICT600%20-%20Chapter%203.pdf
at androidx.core.content.FileProvider$SimplePathStrategy.getUriForFile(FileProvider.java:800)
at androidx.core.content.FileProvider.getUriForFile(FileProvider.java:442)
at com.aaa.mbition.ui.NoteActivity$onCompleteDownloadFile$1$onReceive$1.invoke(NoteActivity.kt:79)
Updated:
Refer to #blackapps 's suggestion. I tried to remove file:// by using replace like below:
val uriString: String = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
val urlFixer = uriString.replace("file://","")
val file = File(urlFixer)
val target = Intent(Intent.ACTION_VIEW)
Log.i("???", "uriString:: $uriString")
Log.i("???", "file:: $file")
Log.i("???", "urlFixer:: $urlFixer")
val uri = FileProvider.getUriForFile(
this#NoteActivity,
"$packageName.provider",
file
)
Below is the Logcat
I/???: uriString:: file:///storage/emulated/0/Download/MBITION/ICT600%20-%20Chapter%203-9.pdf
I/???: file:: /storage/emulated/0/Download/MBITION/ICT600%20-%20Chapter%203-9.pdf
I/???: urlFixer:: /storage/emulated/0/Download/MBITION/ICT600%20-%20Chapter%203-9.pdf
When I pressed OK button, it proceed to PDF Reader, a few milisecond, it kick me back to the apps.

According to #blackapps answer is working. I fixed them by using replace.
val uriString: String = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
val urlFixer = uriString.replace("file://", "").replace("%20", " ")
val file = File(urlFixer)
val target = Intent(Intent.ACTION_VIEW)

Related

How to convert bytearray to pdf and show it with ACTION_VIEW intent

I am receveving a pdf as an encoded base64 String. I would like to show the PDF with ACTION_VIEW intent. How can I do that?
What I have so far is this
val byteArray = Base64.decode(base64String, Base64.DEFAULT)
val file = FileUtils.createFile(requireContext(), "application/pdf")
val fos = FileOutputStream(file)
fos.write(byteArray)
fos.close()
val uri = FileProvider.getUriForFile(requireContext(), requireActivity().getString(R.string.file_provider), file)
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, "application/pdf")
intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
startActivity(intent)
createFile function looks like
fun createFile(context: Context, mimeType: String): File {
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
val fileName = "TMP_" + timeStamp + "_"
val suffix = "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) //.pdf
return context.getExternalFilesDir("Documents")?.let {
if (!it.exists()) {
it.mkdir()
}
File.createTempFile(fileName, suffix, it)
} ?: File.createTempFile(fileName, suffix, Environment.getExternalStorageDirectory())
}
The intent starts properly but when I try to open it with a pdf viewer it says the file is corrupted.
I simple changed this
intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
to
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
works fine now!

Pdf intent not showing file

I'm trying to display in the pdf intent of android an file using his Uri, but it seems that I always get an blank pdf and don't know why, can someone give me some advice why.
Code:
private fun pdfConverter(pdfFile: DtoSymptomCheckerPdf?) {
val documentsPath = File(context?.filesDir, "documents")
if(!documentsPath.exists()){
documentsPath.mkdir()
}
val file = File(documentsPath, pdfFile?.filename)
val pdfAsBytes: ByteArray = android.util.Base64.decode(pdfFile?.file, 0)
val os = FileOutputStream(file,false)
os.write(pdfAsBytes)
os.flush()
os.close()
println("EL CONTENT: " + file.length())
val uri = FileProvider.getUriForFile(App.applicationContext,
BuildConfig.APPLICATION_ID + ".provider", file)
val pdfIntent = Intent(Intent.ACTION_VIEW)
pdfIntent.setDataAndType(uri, "application/pdf")
pdfIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
try {
startActivity(pdfIntent)
} catch (e: ActivityNotFoundException) {
Toast.makeText(requireContext(), "Can't read pdf file",
Toast.LENGTH_SHORT).show()
}
}

Why File(uri.path) work but not uri.toFile() (error: Uri lacks 'file' scheme)

All is in the title.
I'm able to get a File object by using the File constructor but using the toFile() extension on the Uri led to an error.
java.lang.IllegalArgumentException: Uri lacks 'file' scheme: content://***.provider/files/06082021_113127.jpg
What I do :
private var uri: Uri? = null
binding.camera.setOnClickListener {
val name = LocalDateTime.now().generateFileName()
val file = File(requireContext().filesDir, "$name.jpg")
uri = FileProvider.getUriForFile(
requireContext(),
requireContext().packageName + ".provider",
file
)
takePicture.launch(uri)
}
private val takePicture = registerForActivityResult(ActivityResultContracts.TakePicture()) { success: Boolean ->
val test = uri?.path?.let { File(it) }
val test2 = uri?.toFile()
Log.e("test", "${test?.name}")
Log.e("test2", "${test2?.name}")
}
Can someone help shed some light ?

How to get path /storage/emulated/0/Download/file_name.mime_type for Android 10 and above

I am saving a file inside the Downloads directory of the device (Android 11) to be viewed later by my app. I'm allowing multiple file types like pdf, word etc. I was able to save the file like this: (I got this code sample from here)
#TargetApi(29)
private suspend fun downloadQ(
url: String,
filename: String,
mimeType: String
) =
withContext(Dispatchers.IO) {
val response = ok.newCall(Request.Builder().url(url).build()).execute()
if (response.isSuccessful) {
val values = ContentValues().apply {
put(MediaStore.Downloads.DISPLAY_NAME, filename)
put(MediaStore.Downloads.MIME_TYPE, mimeType)
put(MediaStore.Downloads.IS_PENDING, 1)
}
val resolver = context.contentResolver
val uri =
resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
uri?.let {
resolver.openOutputStream(uri)?.use { outputStream ->
val sink = outputStream.sink().buffer()
response.body()?.source()?.let { sink.writeAll(it) }
sink.close()
}
values.clear()
values.put(MediaStore.Downloads.IS_PENDING, 0)
resolver.update(uri, values, null, null)
} ?: throw RuntimeException("MediaStore failed for some reason")
} else {
throw RuntimeException("OkHttp failed for some reason")
}
}
But when I tried to retrieve the file, I tried with the following ways that did not work:
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Downloads._ID)
val id = cursor.getLong(idColumn)
Log.d("uri id ","$id")
val contentUri = ContentUris.withAppendedId(MediaStore.Downloads.EXTERNAL_CONTENT_URI,id)
This approach threw an exception:
java.lang.IllegalArgumentException: Failed to find configured root that contains /external/downloads/78
I got this ID (here 78) from the query and cursor from ContentResolver.query() and I hoped it to return the Uri from which I could get the File.
The second approach was this:
val uri = MediaStore.Downloads.getContentUri("external",id)
uri.path?.let { filePath ->
Log.d("uri path ",filePath)
val file = File(filePath)
} ?: Log.d("uri path ","null")
I used external as the directory based on this answer, but this approach also threw the same exception
java.lang.IllegalArgumentException: Failed to find configured root that contains /external/downloads/78
At the end what ended up working was hardcoding something like this after I used a file explorer app to view the exact directory path:
val file = File("storage/emulated/0/Download/$name.$extension")
So my question is, how do I get the value of this path dynamically, and is this path the same for all devices that can be used like this way?
EDIT: I also wanted to know if I am using the filename and it's extension to view the file, then if user downloads another file with same name then how do I make sure that correct file is opened? (even if i make a separate directory for my app inside Download, user could still download the same file twice that has a name like storage/emulated/0/Download/myDir/file(2).extension )
Try with the following code it will help you.
private fun readFile(){
val FILENAME = "user_details.txt"
val dir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.toString() + "/" + "folderName"
)
} else {
File(
Environment.getExternalStorageDirectory()
.toString() + "/${Environment.DIRECTORY_DOWNLOADS}/" + "folderName"
)
}
dir.apply {
if(this.exists()) File(this, FILENAME).apply {
FileInputStream(this).apply {
val stringBuffer = StringBuffer()
var i: Int
while (this.read().also { i = it } != -1) {
stringBuffer.append(i.toChar())
}
close()
}
}
}
}
You can use
{Environment.DIRECTORY_DOWNLOADS} + "/folderName/file_name.mime_type"
/storage/emulated/0/Download/12d82c65-00a5-4c0a-85bc-238c28005c33.bin

Android Kotlin Write File to 'Files' -> 'Documents' - How do I get a URI?

I'm trying to write a file from my Android App with Kotlin. I see the reference material on the android developer site. Android Developer - Access Documents.... I would like to use what Android is recommending.
When I try and use the context to get the external directory URI, I only get an optional File rather than an URI. As would be expected, I get an error because I'm trying to pass a File? to a URI parameter.
Question is, how do I get the URI to the 'Files' app 'Documents' directory? Alternatively, should I be using a different function call to write the file to 'File' -> 'Documents'
compileSdkVersion 30
buildToolsVersion '30.0.2'
minSdkVersion 27
targetSdkVersion 30
Function from android site.
val CREATE_FILE = 1
private fun createFile(pickerInitialUri: Uri) {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/csv"
putExtra(Intent.EXTRA_TITLE, "myToyBox.csv")
// Optionally, specify a URI for the directory that should be opened in
// the system file picker before your app creates the document.
// putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
}
startActivityForResult(intent, CREATE_FILE)
}
My function call attempt
private fun downloadFile() {
val uri = context?.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) // <- returns a File?
createFile(uri) // <-- needs a URI
}
Sorry not well versed with Kotlin,
String extDir = getContext().getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath();
File folder = new File(extDir, FOLDER_NAME);
try {
final File file = new File(folder, FILE_NAME);
file.createNewFile();
or you could use (if you want Uri),
File myFile = new File(extDir, FOLDER_NAME);
Uri uri = FileProvider.getUriForFile() //last parameter in this method shall be myFile
You cannot just build up an uri yourself. You have to let the user choose a directory or file with ACTION_OPEN_DOCUMENT_TREE, ACTION_OPEN_DOCUMENT, ACTION_CREATE_DOCUMENT, ACTION_GET_CONTENT or ACTION_,PICK.
Once you have done that you can use that uri for INITIAL_URI the next time.
I didn't get the integration to the 'Files' app to work, but I did get access to the "Share" to work allowing me to email the file.
Most of what is the Android page and its links as well as many stackoverflow pages Android Secure Fiile Sharing
High Level
Add the provider to your manifest
Add a res -> xml -> file_path document with the file share 'paths' location
Write the code to share the file using the 'paths' location
Detailed Steps
Update the manifest -> application section with the 'provider' details
<manifest
...
<application
...
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.com.YOURDOMAIN.YOURAPP.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
Add a res -> xml -> file_path document with the file share 'paths' location
If you don't already have an xml directory/package under the res location (i.e., where your layouts are, add it and add a file. I called mine file_paths. You may already have one depending on what you already coded. I wrote my document to share to the docs/ folder. This locaiton will be needed in the actual code.
docs/ is located File(requireContext().filesDir, "docs")
docs/ is located in the emulator under data/data/com.YOURDOMAIN.YOURAPP/files/docs
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_docs" path="docs/"/>
</paths>
Write the code to share the file using the 'paths' location and the fileprovider reference
private fun downloadFile() {
val filePath: File = File(requireContext().filesDir, "docs")
filePath.mkdirs()
val newFile = File(filePath, "MyToyBox.csv")
newFile.delete()
newFile.createNewFile()
val contentUri = FileProvider.getUriForFile(
requireContext(),
"com.YOURDOMAIN.YOURAP.fileprovider",
newFile
)
val CSV_HEADER = "Series,My Toy Box ID,Debut Year,Phase,Wave,Action Figure Package Name," +
"Movie Scene," +
"Estimated New Value,Have the New Figure,Have New Count," +
"Want the New Figure,Want New Count," +
"Sell the New Figure,Sell New Count," +
"Order the New Figure,Order New Count," +
"Order from Text," +
"Have the Loose Figure,Have Loose Count" +
",Want the Loose Figure,Want Loose Count" +
",Sell the Loose Figure,Sell Loose Count" +
",Notes" + "\n"
var fileWriter: FileWriter? = null
try {
fileWriter = FileWriter(newFile)
fileWriter.append(CSV_HEADER)
figureList.forEach { figure ->
val specifics = figure.specifics
val noText = "No"
val yesText = "Yes"
val new_haveCount = specifics.new_haveCount.toString()
val new_wantCount = specifics.new_wantCount.toString()
val new_sellCount = specifics.new_sellCount.toString()
val new_orderCount = specifics.new_orderCount.toString()
val new_orderText = specifics.new_orderText
val loose_haveCount = specifics.loose_haveCount.toString()
val loose_wantCount = specifics.loose_wantCount.toString()
val loose_sellCount = specifics.loose_sellCount.toString()
// set yes/no text based on count
val new_haveTheFigure = if (specifics.new_haveCount == 0) noText else yesText
val new_wantTheFigure = if (specifics.new_wantCount == 0) noText else yesText
val new_sellTheFigure = if (specifics.new_sellCount == 0) noText else yesText
val new_orderTheFigure = if (specifics.new_orderCount == 0) noText else yesText
val loose_haveTheFigure = if (specifics.loose_haveCount == 0) noText else yesText
val loose_wantTheFigure = if (specifics.loose_wantCount == 0) noText else yesText
val loose_sellTheFigure = if (specifics.loose_sellCount == 0) noText else yesText
val notes = specifics.notes
// formatted value
var newValueString = kUnknownMTBValue
val currencyFormat = NumberFormat.getCurrencyInstance()
currencyFormat.maximumFractionDigits = 2
currencyFormat.currency = Currency.getInstance("USD")
if (figure.saleSummary != null) {
val formattedValue =
currencyFormat.format(figure.saleSummary!!.estimatedValueMean).toString()
newValueString =
context?.getString(R.string.saleSummaryValueCount,
formattedValue,
figure.saleSummary!!.saleCount).toString()
}
// need to escape , with \" in front and back, such as in wave and names.
val row: String = "\"${figure.series.seriesName}\"," +
"${figure.figureUniqueId}," +
"${figure.debutYear}," +
"\"${figure.phase}\"," +
"\"${figure.wave}\"," +
"\"${figure.figurePackageName}\"," +
"\"${figure.scene}\"," +
"\"$newValueString\"," +
"$new_haveTheFigure,$new_haveCount," +
"$new_wantTheFigure,$new_wantCount," +
"$new_sellTheFigure,$new_sellCount," +
"$new_orderTheFigure,$new_orderCount," +
"\"$new_orderText\"" +
",$loose_haveTheFigure,$loose_haveCount," +
"$loose_wantTheFigure,$loose_wantCount," +
"$loose_sellTheFigure,$loose_sellCount," +
"\"$notes\"" + "\n"
fileWriter.append(row)
}
} catch (e: Exception) {
println("Writing CSV error!")
e.printStackTrace()
}
fileWriter!!.close()
createFile(contentUri)
}
private fun createFile(pickerInitialUri: Uri) {
val shareIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, pickerInitialUri)
type = "text/plain"
setDataAndType(pickerInitialUri, requireContext().contentResolver.getType(pickerInitialUri))
}
startActivity(Intent.createChooser(shareIntent, "My Toy Box"))
}

Categories

Resources