this is my code
fun createDirDownload(): File {
val fileDir = activity?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)!!
if (!fileDir.exists()) {
fileDir.mkdirs()
}
return fileDir
}
The firebase crashlytics shows this method: getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) caused ANR.
But I don't know why and how to solve the problem.
How to solve this issue?
Related
I download a file using Retrofit and save it in a subfolder in the download directory.
when I check with the phone's file manager, it is downloaded and saved correctly. For example, in the following path:
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path + "/MyApp"
But when I open the file manager with intent like ACTION_GET_CONTENT or ACTION_OPEN_DOCUMENT the downloaded file is not visible.
In addition, if I rename the file or copy and paste it through the phone's file manager, everything will be fixed in the same path
Also, saving in the downloads folder is done without any problem
But when the subfolder is created and it is supposed to be saved there, this problem occurs
There is no problem with downloading by DownloadManager, but i want use retofit
Download function:
suspend fun download(url: String, targetPath: String, progressRetrofit: RetrofitProgress) = flow {
try {
val response = apiService.download(url).awaitResponse()
val body = response.body()
if (response.isSuccessful && body != null) {
try {
val file = File(targetPath)
body.byteStream().use { inputStream ->
FileOutputStream(file).use { outputStream ->
val data = ByteArray(1024)
var read: Int
var currentDownloadSize = 0L
val fileSize = body.contentLength()
while (inputStream.read(data).also { read = it } != -1) {
outputStream.write(data, 0, read)
currentDownloadSize += read
withContext(Dispatchers.Main)
{
progressRetrofit.onProgressUpdate((currentDownloadSize * 100 / fileSize).toInt(), fileSize, currentDownloadSize)
}
}
withContext(Dispatchers.Main)
{
progressRetrofit.onProgressUpdate((currentDownloadSize * 100 / fileSize).toInt(), fileSize, currentDownloadSize)
}
outputStream.close()
outputStream.flush()
}
}
emit(NetworkResult.Success(true))
} catch (e: Exception) {
emit(NetworkResult.Failure(e.message.toString()))
errorMessage(e.message.toString(), true)
}
} else {
emit(NetworkResult.Failure(response.message()))
errorMessage(response.errorBody().toString(), true)
}
} catch (e: Exception) {
emit(NetworkResult.Failure(e.message.toString()))
errorMessage(e.message.toString(), true)
}
}
Hmmmm...
You are right...
It happens.
But only if with ACTION_OPEN_DOCUMENT the user directly uses the Downloads item.
Instead the user should browse the device, and go to Download directory and then to the subdirectory.
(Note: The first ends with an s, the real directory is without s.).
After some more tests it appeared if the file was scanned by the MediaStore.
So add some few lines of code to let it be scanned after download.
Since Android 6.0 there are bunch of changes in file sharing behaviour.
Please take a look on FileProvider and look further through API changes in the official android documentation, e.g. like this.
Without extra details in your code or even reproducible code sample I can not help more.
I use PdfRenderer to render PDF preview in Android API 25:
var parcelFileDescriptor: ParcelFileDescriptor? = null
var pdfRenderer: PdfRenderer? = null
var firstPage: PdfRenderer.Page? = null
try {
val file = File(filePath)
parcelFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
pdfRenderer = PdfRenderer(parcelFileDescriptor) // it throw exception or crash from here
firstPage = pdfRenderer.openPage(0)
// Do something with firstPage
} catch (e: Exception) {
e.printStackTrace()
} finally {
firstPage?.close()
pdfRenderer?.close()
parcelFileDescriptor?.close()
}
But in first call, it throws java.lang.SecurityException: cannot create document. Error: 4. From 2nd or 3rd call, it is not responding and crash in native.
I just call it from only one thread, so there is no concurrency issue. Can anyone help me?
I found trying to open a password protected pdf on Android < P causes the crash: https://issuetracker.google.com/issues/37052344.
What to do is checking if the pdf file is encrypted or not before opening.
I know there are many similar topics like this, but they are either outdated or do not work with pdfs.
My question is, how do I download a pdf from cloud firestore and save it into the internal storage? My normal method was to use downloadmanager and save the pdf into external storage, but since this does not work anymore because of scoped storage, I need to find a new way.
Currently, I only know how to create a temporary file and download the pdf from firestore into this temporary file, but not how to save it.
Old Method (with Downloadmanager)
class PDFDownloader(private val context: Context) {
private val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
private val downloadDirectory = Environment.DIRECTORY_DOCUMENTS
private val authority = "${BuildConfig.APPLICATION_ID}.fileprovider"
private val pdfIntent = Intent(Intent.ACTION_VIEW).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
// DOWNLOADING THE PDF INTO EXTERNAL STORAGE
suspend fun downloadPDF(pdfUri: String, fileName: String) {
val pdfUrl = Firebase.storage.getReferenceFromUrl(pdfUri).downloadUrl.await()
// NOT WORKING ANYMORE BECAUSE OF SCOPED STORAGE
val request = DownloadManager.Request(pdfUrl)
.setTitle(fileName)
.setDestinationInExternalFilesDir(context, downloadDirectory, "$fileName.pdf")
downloadManager.enqueue(request)
}
// RETRIEVING THE PDF FROM EXTERNAL STORAGE
suspend fun getPDFFileAndOpen(fileName: String) {
val regex = "$fileName.pdf"
withContext(Dispatchers.IO) {
val fileList = context.getExternalFilesDir(downloadDirectory)?.listFiles()
val file = fileList?.find { it.name == regex }
pdfIntent.setDataAndType(FileProvider.getUriForFile(context, authority, file), "application/pdf")
context.startActivity(intent)
}
}
}
New Method (without Downloadmanager)
class PDFDownloaderWithoutManager(private val context: Context) {
override suspend fun downloadPDF(uri: StorageReference, fileName: String) {
withContext(Dispatchers.IO) {
// Step 1: Creating a temporary file
val tempFile = File(context.filesDir, "$fileName.pdf")
// Step 2: Downloading the pdf from cloud-firestore into tempFile
uri.getFile(tempFile).await()
// Step 3: Saving the file into internal storage
// OR SAVING INTO EXTERNAL STORAGE WITH SCOPED STORAGE
// (I take whats easier)
?????????????????????????????????????????????????????
}
}
}
// RETRIEVING THE PDF FROM INTERNAL STORAGE
suspend fun getPDFFileAndOpen(fileName: String) {
val regex = "$fileName.pdf"
????????????????????????????????????????????????????????????
}
There is also a method uri.getFile("URI TO SAVE FILE TO), but I don't know how to use that either.
Edit
Using download manager works as intended, and it successfully downloads the file from firestore (yay). The only problem I have is, that I can't open the pdf anymore at android 11. I get the following error:
android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=content://com.example.app.fileprovider/external_files/Documents/Kalibrierung und Überprüfung.pdf typ=application/pdf flg=0x10000001 }
at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:2067)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1727)
at android.app.ContextImpl.startActivity(ContextImpl.java:1023)
at android.app.ContextImpl.startActivity(ContextImpl.java:994)
at android.content.ContextWrapper.startActivity(ContextWrapper.java:403)
at com.example.app.business.domain.validator.document.FileLegacyValidator.openPdf(FileLegacyValidator.kt:38)
at com.example.app.business.domain.validator.document.FileLegacyValidator.openPDFFileOrNull(FileLegacyValidator.kt:35)
at com.example.app.presentation.documents.DocumentViewModel$setStateEvent$1$1.invokeSuspend(DocumentViewModel.kt:36)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
This is not working: context.startActivity(intent)
Edit: Answer not relevant after your latest edit to question.
If you're going to use external storage, here's how to do it:
val isExternalStorageWritable =
(Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED)
if (!isExternalStorageWritable ) {
// handle error: Storage not available
return
}
val rootFolder = context.getExternalFilesDir(null)
val downloadsFolder = File(rootFolder, "downloads")
val isDownloadsFolderCreated = downloadsFolder.mkdirs()
if (!isDownloadsFolderCreated) {
// handle error: unable to create folder
return
}
val tempInputStream = FileInputStream(tempFile)
val targetFile = File(downloadsFolder, "targetFileName.pdf")
val isTargetFileCreated = targetFile.createNewFile()
if (!isTargetFileCreated) {
// handle error: unable to create file
return
}
FileOutputStream(targetFile).use { outputStream ->
tempInputStream.copyTo(outputStream)
}
Make sure to call this from a background thread or IO coroutine.
I've fixed this problem. I always thought that my emulator had a PDF reader already installed (since the other emulator had it too), but it didn't. The activity couldn't be opened because there was no PDF reader.
Fix: Install PDF Reader.
I am trying to save/export a file on the user Documents shared folder so it can be persistent if the application is deleted (it's an export of the user work on the application). Following the official documentation to create a save a file on the shared folder, I have this basic implementation:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
registerActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ result ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.data?.let { writeInFile(it, "this is a test") }
}
}
}
private fun createFile() {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply{
addCategory(Intent.CATEGORY_OPENABLE)
type = "text/plain"
putExtra(Intent.EXTRA_TITLE, "filename.txt")
putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.DIRECTORY_DOCUMENTS)
}
startActivityForResult(intent, WRITE_REQUEST_CODE)
//this.registerActivity.launch(intent)
}
private fun writeInFile(uri: Uri, text: String) {
val outputStream: OutputStream
try {
Log.i("export", uri.toString())
outputStream = contentResolver.openOutputStream(uri)!!
val bw = BufferedWriter(OutputStreamWriter(outputStream))
bw.write(text)
bw.flush()
bw.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
The createFile function is called from a click event to export the data.
The Activity for choosing the folder to save is launching, but saving is failing. I am getting the following error:
2021-06-23 18:24:43.268 2853-2871/? E/DatabaseUtils: Writing exception to parcel
java.lang.IllegalArgumentException: Parent document isn't a directory
at com.android.internal.content.FileSystemProvider.createDocument(FileSystemProvider.java:244)
at com.android.providers.downloads.DownloadStorageProvider.createDocument(DownloadStorageProvider.java:207)
at android.provider.DocumentsProvider.callUnchecked(DocumentsProvider.java:1124)
at android.provider.DocumentsProvider.call(DocumentsProvider.java:1067)
at android.content.ContentProvider.call(ContentProvider.java:2448)
at android.content.ContentProvider$Transport.call(ContentProvider.java:517)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:295)
at android.os.Binder.execTransactInternal(Binder.java:1154)
at android.os.Binder.execTransact(Binder.java:1123)
2021-06-23 18:24:43.269 1559-1680/? W/DocumentsContract: Failed to create document
java.lang.IllegalArgumentException: Parent document isn't a directory
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:172)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:142)
at android.content.ContentProviderProxy.call(ContentProviderNative.java:732)
at android.content.ContentProviderClient.call(ContentProviderClient.java:603)
at android.content.ContentResolver.call(ContentResolver.java:2395)
at android.provider.DocumentsContract.createDocument(DocumentsContract.java:1371)
at com.android.documentsui.DocumentsAccess$RuntimeDocumentAccess.createDocument(DocumentsAccess.java:157)
at com.android.documentsui.picker.CreatePickedDocumentTask.run(CreatePickedDocumentTask.java:79)
at com.android.documentsui.picker.CreatePickedDocumentTask.run(CreatePickedDocumentTask.java:42)
at com.android.documentsui.base.CheckedTask.doInBackground(CheckedTask.java:65)
at android.os.AsyncTask$3.call(AsyncTask.java:394)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at com.android.documentsui.ProviderExecutor.run(ProviderExecutor.java:104)
I just found that the issue wasn't in my implementation, but my Android emulator. I tried to create a folder from the file manager (outside my application) and I was getting the same error. So, I tried testing my application on a physical device, it worked perfectly!
The issue with my emulator was that the SD card was corrupted (either it wasn't set up or something went wrong at some point without me realizing it). Resetting that fixed the issue
I am trying do some tests with Environment.getExternalStoragePublicDirectory in Android 11.
Question 1: Why I can't create a new file but a new directory I can do?
Question 2: canRead() + canWrite() return true, but createNewFile() does not work, why?
(if null, if exists, ... checks are omitted for this example)
Get public movies dir:
fun getExternalPublicMoviesDir(): File? {
val movies = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)
try {
movies.mkdirs()
if (Environment.getExternalStorageState(movies) == Environment.MEDIA_MOUNTED) {
return movies
}
} catch (e: Exception) {
e.printStackTrace()
}
return null
}
Create files/folders:
val externalMoviesPath = getExternalPublicMoviesDir()
externalMoviesPath.canRead() //return true
externalMoviesPath.canWrite() //return true
val newDir = File(externalMoviesPath, "newDirTest")
newDir.mkdirs() //works
val newFile = File(externalMoviesPath, "newFileTest")
newFile.createNewFile() // <- java.io.IOException: No such file or directory
Exception:
java.io.IOException: No such file or directory
at java.io.UnixFileSystem.createFileExclusively0(Native Method)
at java.io.UnixFileSystem.createFileExclusively(UnixFileSystem.java:317)
at java.io.File.createNewFile(File.java:1008)
Just for completeness: In Android 10 code works when WRITE_EXTERNAL_STORAGE is granted.
UPDATE
When I use Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) instead of Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), createNewFile method is working.
Tested on real devices like Samsung, Pixel and on Android emulator.
You can user FileProvider with Context.getExternalFilesDir(type) where type can be directory etc... DIRECTORY_MOVIES from now and set path as <external-files-path name="" path="/" /> in xml directory. For Further detail https://developer.android.com/reference/androidx/core/content/FileProvider