Use case: download file from my app open it with appropriate editing application and after user is done with editing upload the file back to my app.
Example: I download the docx file from my app to public folder (eg. 'Documents') and after getting uri with FileProvider I send ACTION_VIEW intent (giving write permissions). Then open it with word app and edit it. But then the problem arives - word app says I need to save the file as a new copy and doesn't let me override the original file. Strange thing is if I open that downloaded file from file browser everything works fine and I can override the file. But when I use Intent from my app word application decides to create a copy inside their private directory. Why are they doing it this way? Is there any possibility to either get the newly saved file back to my app (from startActivityForResult) or somehow make them override the original file in the public folder?
Edit: As blackapps sugested I tried to check intents with 'Intent-Interceptor'. Here are the results:
My application:
intent://com.android.externalstorage.documents/tree/primary%3ADocuments/document/primary%3ADocuments%2FDocument.docx#Intent;scheme=content;type=application/vnd.openxmlformats-officedocument.wordprocessingml.document;launchFlags=0x13000000;end
------------
ACTION: android.intent.action.VIEW
DATA: content://com.android.externalstorage.documents/tree/primary%3ADocuments/document/primary%3ADocuments%2FDocument.docx
MIME: application/vnd.openxmlformats-officedocument.wordprocessingml.document
URI: intent://com.android.externalstorage.documents/tree/primary%3ADocuments/document/primary%3ADocuments%2FDocument.docx#Intent;scheme=content;type=application/vnd.openxmlformats-officedocument.wordprocessingml.document;launchFlags=0x13000000;end
FLAGS:
FLAG_RECEIVER_FOREGROUND
FLAG_ACTIVITY_FORWARD_RESULT
FLAG_ACTIVITY_PREVIOUS_IS_TOP
------------
MATCHING ACTIVITIES:
Word (com.microsoft.office.word - com.microsoft.office.word.WordActivity)
Default file browser (Google 'Files'):
intent://com.google.android.apps.nbu.files.provider/1/file%3A%2F%2F%2Fstorage%2Femulated%2F0%2FDocuments%2FDocument.docx#Intent;scheme=content;type=application/vnd.openxmlformats-officedocument.wordprocessingml.document;launchFlags=0x3000000;end
------------
ACTION: android.intent.action.VIEW
DATA: content://com.google.android.apps.nbu.files.provider/1/file%3A%2F%2F%2Fstorage%2Femulated%2F0%2FDocuments%2FDocument.docx
MIME: application/vnd.openxmlformats-officedocument.wordprocessingml.document
URI: intent://com.google.android.apps.nbu.files.provider/1/file%3A%2F%2F%2Fstorage%2Femulated%2F0%2FDocuments%2FDocument.docx#Intent;scheme=content;type=application/vnd.openxmlformats-officedocument.wordprocessingml.document;launchFlags=0x3000000;end
FLAGS:
FLAG_ACTIVITY_FORWARD_RESULT
FLAG_ACTIVITY_PREVIOUS_IS_TOP
------------
MATCHING ACTIVITIES:
Word (com.microsoft.office.word - com.microsoft.office.word.WordActivity)
Here's how I obtain directory in which I save files (I use ActivityResultContracts):
directoryResultLauncher = registerForActivityResult(OpenDocumentTree()) { uri ->
if (uri == null) return#registerForActivityResult
requireContext().contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
authentication.publicDirectory = uri.toString()
}
Download method:
val directoryUriString = authentication.publicDirectory
val directory = DocumentFile.fromTreeUri(context, directoryUriString.toUri())
val file = directory!!.createFile(mimeType, title)
val fileResult = getFileContent(context.contentResolver.openOutputStream(file!!.uri)!!)
if (!fileResult.isError()) {
val intent = Intent(Intent.ACTION_VIEW, file.uri).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
startActivity(intent)
}
Below, I will try to suggest you possible causes and solutions for your encountered issue:
First, maybe the difference at the URI source level!
It looks like your app use a Uri like this one:
content://com.android.externalstorage.documents/tree/...
which is a TreeDocument uri from a DocumentFile
While the Explorer app uses a FileProvider Uri from a Io.File.
Second, maybe you have not submitted the grand_write_access flag
When you send the Uri to the word app, ensure you to add the flag:
Intent.FLAG_GRANT_WRITE_URI_PERMISSION that will allow the word app to get the write access on the resource.
Maybe the word app behaves differently depending on whether or not it has the write access permission on the submitted file.
Hope this enlightened you a bit!
Related
I meet some problems when I'm trying to share some files among apps. In app A, one can choose to open an existing file via openDocument():
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument()
) {
// In short, preserve returned uri
uri = it
}
...
onClick = {
launcher.launch(arrayOf("text/plain"))
},
Then, app A can share the uri to other apps by:
val shareIntent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, uri)
type = "text/plain"
}
context.startActivity(Intent.createChooser(shareIntent, "Share test"))
Since Uri returned from SAF just starts with "content://", FileProvider is not used here.
Then something weired happened. For some apps, it works just fine; however for others, I always receive a message saying the file does not exist, or it cannot be shared properly. For example, I get an uri like: content://com.android.providers.downloads.documents/document/442
Let's say its correct file name is some.txt, But in app B which accepts the shared file, the file name is mistaken as 442 with no extension name.
I wonder what causes this problem. Is it my code's, SAF's, or those other apps' fault? And how can I correct it?
My SDK version is 32, and I'm using jetpack compose for app A.
however for others, I always receive a message saying the file does not exist, or it cannot be shared properly
You are not doing anything to grant permission to other apps to be able to read the content identified by that Uri. The best approach is to use ShareCompat.IntentBuilder to create your Intent. Alternatively, use addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION). See this blog post for more.
Is it my code's, SAF's, or those other apps' fault?
In terms of "a message saying the file does not exist", that is because of the aforementioned permission problem.
In terms of "the file name is mistaken as 442 with no extension name", if that does not clear up once you fix the permission problem, the bug is in the other app.
So I have an android app which opens and displays PDF's, I have the user select pdfs like this
fun openFile() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/pdf"
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("application/pdf"))
putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Files.getContentUri("external"))
}
activity?.startActivityForResult(intent, REQUEST_CODE_PDF_FILE)
}
And then I retrieve the URI from the activity result display it and save the URI. However the next time the app is opened I want to be open that same file, right now when I try opening the saved URI I get the following:
ava.lang.SecurityException: Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord{c3dfcb2 32587:ca.thing.testapp/u0a237} (pid=32587, uid=10237) requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
at android.os.Parcel.createExceptionOrNull(Parcel.java:2425)
at android.os.Parcel.createException(Parcel.java:2409)
at android.os.Parcel.readException(Parcel.java:2392)
at android.os.Parcel.readException(Parcel.java:2334)
at android.app.IActivityManager$Stub$Proxy.getContentProvider(IActivityManager.java:5850)
at android.app.ActivityThread.acquireProvider(ActivityThread.java:6973)
So clearly after closing and reopening the app I no longer have permission to use that selected file. So I imagine what I need to do is make a copy of that file into some cache dir that I do have permissions in so that I can display it when the app is reopened. How would I go about doing that?
You should take persistable uri permission in onActivityResult in order to use the uri later.
Making a copy is not needed.
Note: all type of files
I used default Gallery Intent to show to storage but it shows the Goggle Drive Option along with local Storage
I tried the following referals but nothing works for me.
Ref:
How to let user select only local files using Intent in Android?
2 .https://stackoverflow.com/questions/27762377/android-why-intent-extra-local-only-shows-google-photos
Is it possible to hide google drive while using Intent.ACTION_GET_CONTENT in android?
Used another library, to show the only local storage, it works fine
but Requirement is, Need to show only local storage and its all file types along with file size which is not available in this library.
Kindly suggest some other library like below with showing file size
Ref:
https://github.com/codekidX/storage-chooser
https://androidexample365.com/lets-user-choose-files-in-internal-or-external-storage-with-just-few-lines-of-code/
Use the below code that will help you achieve the desired results:
val selectedUri =
Uri.parse(Environment.getExternalStorageDirectory().toString() + "/Pictures")
val intent = Intent(Intent.ACTION_PICK)
intent.data = selectedUri
intent.type = "image/*"
if (intent.resolveActivityInfo(context!!.packageManager, 0) != null) {
startActivityForResult(
intent,
101
)
} else {
// if you reach this place, it means there is no any file
// explorer app installed on your device
}
I'm making an app where you are able to select images or documents from storage and they are then displayed in a list kinda like Google Drive so you can choose one and it opens and you can view it. This list is implemented using a RecyclerView, and I store the uri and file type in a json in SharedPreferences. I get the content using:
val getFile = registerForActivityResult(
ActivityResultContracts.GetContent()
) {
if (it != null) {
val fileLink = FileLink()
fileLink.setUri(it.toString())
fileLink.setType(contentResolver.getType(it)!!)
fileLink.setName(getFileName(it)!!)
list.add(fileLink)
presenter.saveChangesToList(list)
adapter.notifyDataSetChanged()
}
}
And then I just call:
getFile.launch("image/*|application/pdf")
This didn't let me pick stuff from the internal storage (the files appeared grayed out) and it didn't actually filter the selectable files. It worked perfectly with images taken with the camera though.
To fix the "not being able to pick stuff from storage" I changed GetContent() to OpenDocument():
val getFile = registerForActivityResult(
ActivityResultContracts.OpenDocument()
) {
if (it != null) {
val fileLink = FileLink()
fileLink.setUri(it.toString())
fileLink.setType(contentResolver.getType(it)!!)
fileLink.setName(getFileName(it)!!)
list.add(fileLink)
presenter.saveChangesToList(list)
adapter.notifyDataSetChanged()
}
}
And then I call:
getFile.launch(
arrayOf(
"application/pdf",
"image/*"
)
)
With this, I am able to select files from storage and it also lets me restrict so you can only pick either images or pdfs. My problem now is that, if I get stuff like this, I can open the document once. If I close my app and open it again and try to open the document again, I get this error:
java.lang.SecurityException: UID 10156 does not have permission to content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2FNewPdf
I open the documents using this code:
viewHolder.itemView.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(
Uri.parse(fileList[position].getUri().toString()),
fileList[position].getType()
)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
context.startActivity(intent)
}
Could I get some help with this? I'm guessing it has to do with the permission to read uris, but I have no idea how to fix it. I also don't understand why I can open the files correctly the first time, but not after I restart the app.
You did not call takePersistableUriPermission() on a ContentResolver as soon as you were handed the Uri to the selected content.
As a result, your rights to access the content will expire — your rights to access the content will be limited to the one activity instance that received the Uri, not other activities or future app processes.
After about a week of pulling my hair out, I'm finally done and ready to ask for some help.
Basically in my app I use the Intent below to create a new PDF, which is done via Storage Access Framework.
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "application/pdf"
intent.putExtra(Intent.EXTRA_TITLE, title)
startActivityForResult(intent, 1234)
After that I get the Uri on the onActivityResult() method, like so:
uri = dataIntent.data
if (uri != null) {
val takeFlags = data.flags and (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
contentResolver.takePersistableUriPermission(uri, takeFlags)
generatePdf(uri)
}
PDF generation is ok, the problem comes when I need to call ACTION_VIEW for the user to see the generated file or to share the file using ACTION_SEND.
Example of ACTION_VIEW usage (Yes, I'm using both Kotlin and Java):
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(uri, mimeType);
startActivity(intent);
I can't for the life of me figure out how to get an Uri that another app can use.
What I tried so far:
This answer, but the following exception is thrown: java.lang.IllegalArgumentException: column '_data' does not exist. Available columns: [_display_name, _size]
DocumentFile, using DocumentFile.fromFile(file), which turns the Uri from content://com.myapp.provider/root/document/primary:folder-created-by-the-user/generated-pdf.pdf to file:///root/document/primary:folder-created-by-the-user/generated-pdf.pdf, and still no app can open it
Many many other things that I can't even remember anymore
If someone could shed some light on this issue would be truly appreciated.
In principle use the same uri as obtained at creating the file. But ...you cannot grant a read uri permission on that uri. You got it. But you cannot forward such a permission to a viewer of your document.
Instead you should implement a ContentProvider. Then you can serve the content of your file.
Like blackapps said in his response, what I had to do was implement a ContentProvider, more specifically a DocumentProvider.
Following this link and this link is what finally did the trick. I implemented a CustomDocumentProvider that exposes a folder inside my app's private files (context.getFilesDir().getAbsolutePath() + "/folderToExpose"), after that all files created in this folder were exposed to other apps and I could use ACTION_VIEW and ACTION_SEND normally.
If someone happens to come across this issue, just make sure that the folder you want to expose doesn't contain any files that are crucial to your app, like database files, since users will have full access to all of its contents. And if it is a new folder, make sure to create it by calling mkdirs().