In my app, I'd like to store a persistent read permission to content provided by Dropbox (among other content providers). The Android Dropbox app doesn't support the Storage Access Framework, so to be able to select content I can't use ACTION_OPEN_DOCUMENT -- instead I need to use ACTION_GET_CONTENT.
However, it seems that some content providers, such as Drive, don't return persistable permissions for URIs returned via ACTION_GET_CONTENT. I believe this is as expected, because GET_CONTENT URIs are not supposed to be persistable. Unfortunately I do need to persist the reference across restarts.
It seems that there is no way to get persistent permissions to a URI in recent API versions if the content provider doesn't support SAF. Is that true? What is a good workaround?
Bad (for my use case) workarounds would be: copying the content and storing it locally, relying on implementation details which are not in spec (e.g. it seems that Dropbox URIs returned by GET_CONTENT are in fact persistable), or not persisting the permission.
It seems that there is no way to get persistent permissions to a URI in recent API versions if the content provider doesn't support SAF. Is that true?
Based on my experiments, yes. More accurately, AFAICT, only Uri values obtained from a DocumentsProvider have a shot at having persistable permissions, in terms of what the framework offers. I do not see how an ordinary ContentProvider can offer this.
What is a good workaround?
Given your list of "bad" workarounds, your best workaround is to use some Dropbox-specific API to allow the user to choose the content and for you to access it over time, if Dropbox offers one.
Of the "bad" workarounds, copying the content is a likely choice — adjust your UI to tell the user that you are "importing" the content, for example, to help indicate that it is indeed a copy.
Related
URI returned by ActivityResultContracts.OpenDocument can be "opaque" (I don't know a better term) in a sense that you can't write to the associated file using it (openOutputStream throws IllegalArgumentException with message "Media is read-only"), you can't determine its parent directory etc. because it is in the form of e.g. "/document/image:821286". I can read the file data using it though, and not all URIs returned by OpenDocument launcher are "opaque".
I know that I do have all the access I need to all those files, because if the same file is returned by ActivityResultContracts.CreateDocument the returned URI is never "opaque", so you can write to it, get the containing directory etc. In that case the URI is something like "/document/primary:DCIM/Camera/IMG_20211007_101132.jpg".
I do have WRITE_EXTERNAL_STORAGE permission granted and this is being tested on Android 9.
I am looking for a way to either always get "transperent" URIs from OpenDocument launcher or to convert "opaque" to "transparent" URI; please write below how to accomplish either of those.
in a sense that you can't write to the associated file using it (openOutputStream throws IllegalArgumentException with message "Media is read-only")
That is an unfortunate limitation of the Storage Access Framework: poorly-written document providers can elect to not allow read-write access to the content, and we have no means of specifying that we only want writeable documents. We have to gracefully fail in those cases.
because if the same file is returned by ActivityResultContracts.CreateDocument the returned URI is never "opaque"
That is not true. You tested with one document provider out of many, and that document provider unfortunately leaks information. There is no requirement for any given device to have a document provider that behaves that way, and there is no requirement that the user use such a document provider.
Document providers do not have to work with publicly-accessible filesystem paths, let alone tell you what those paths are. Document providers are welcome to store documents on network servers, cloud storage providers (e.g., Google Drive), encrypted data stores, etc.
I am looking for a way to either always get "transperent" URIs from OpenDocument launcher or to convert "opaque" to "transparent" URI; please write below how to accomplish either of those.
That is not possible.
Despite this being a simple question I cannot find the answer on google or stackoverflow.
When I use the following code I get this result //com.android.externalstorage.documents/tree/primary:Podcasts
var intent = new Intent(Intent.ActionOpenDocumentTree);
intent.PutExtra("android.content.extra.SHOW_ADVANCED", true);
intent.PutExtra("android.content.extra.FANCY", true);
intent.PutExtra("android.content.extra.SHOW_FILESIZE", true);
Can you help me understand the parts of my result?
How Android Storage works?
To ensure security between Android apps, Android didn't let you directly access every file within the storage system. They have something called ContentProvider.
Think of this content provider like a waiter, that your apps can ask for a certain file/folder (through Content Uri).
Content Uri will look like this: content://[Authority]/[path]/[id] is just an example of Content Uri. com.android.externalstorage.documents is an example of authority (for access to External Storage providers).
So in your case, your Uri will gain you access to the directory of Podcasts in your External Storage.
By having Uri, you can communicate between apps or service provider easily without having to pass real file every time you ask or give one. Just pass a lightweight simple Uri.
What happened in your code?
If you're wondering what happens in your code, try to look at the Reference.
It says:
Allow the user to pick a directory subtree. When invoked, the system will display the various DocumentsProvider instances installed on the device, letting the user navigate through them. Apps can fully manage documents within the returned directory.
I'm not sure what you're trying to achieve (please clarify if you can, so I can help out), but I hope my answer contains enough information.
How can we ensure that certain applications are not able to access my data stored in content provider where in certain other applications can access that? Basically I need to allow some application of my interest to access my data stored in Content Provider but I do not want all the applications to be able to access that data. How can I achieve this?
Thanks.
The easiest way is to protect the content provider with a permission you define. Make it a signature a permission so only apps signed with your certificate are allowed to get it.
See:
http://developer.android.com/guide/topics/security/security.html
http://developer.android.com/reference/android/R.styleable.html#AndroidManifestProvider
http://developer.android.com/guide/topics/manifest/provider-element.html
If doing this based on certificates is not sufficient, you will need to write the permission checks yourself. This is done by calling Binder.getCallingUid() for incoming calls to your applications, and deciding whether the given uid has permission to access your provider. Actually implementing a different policy that is actually secure requires a lot of careful thought and design, though.
In the AndroidManifest.xml, at the screen with the properties of your ContentProvider, you have two fields:
Read Permission
WritePermission
So, you can define secure strings (also it may be path to some file) that are permissions for acces to your ContentProvider.
Applications that want to access your content provider must have that ones added in their UsesPermission elements.
When querying a ContentProvider on Android, one specifies the ContentProvider of interest by providing the "content URI" for that ContentProvider. What happens when multiple ContentProvider's serve that same URI, either intentionally? or maliciously?
When trying to open a pic on my phone, I've seen it prompt with apps that can "handle" the image. Will the same kind of thing happen here?
Multiple ContentProviders can't do this. The first application that registers a content provider using the element in its manifest has control over the URI pattern. I'm pretty sure that you'll get an installation error if you try to add another provider that uses the same URI pattern. Android keeps track of providers and URIs.
When you see a prompt with multiple apps for handling a file or situation, that's because the apps have specified an with a child that includes
android.intent.category.CATEGORY_ALTERNATIVE or android.intent-category.CATEGORY_SELECTED_ALTERNATVE. In essence, the app or apps are declaring themselves to be alternatives to the action specified in the child. This allows the user to have multiple choices for handling a type of data.
It makes sense to provide alternatives: a user might want to edit a picture, share it via Twitter, or e-mail it.
Note that two content providers can do the same thing, but they can't use the same URI. An app has to make a conscious choice of which one to use, or provide some mechanism of choosing between the two.
For instance, let's take android.permission.GET_PACKAGE_SIZE.
Searching for this string in Android 1.6 *.xml source files only points to a single application that uses it, frameworks\base\tests\AndroidTests.
So the next step is to search through the .java files in a hope that I'll eventually find the code that might look like it queries for package size.
Is this the supposed way of discovering permission use?
The Manage Applications UI uses the API protected by this permission. It probably doesn't request the permission in its manifest because it runs as the system user ID so is implicitly getting the permission.
For applications in general, yes you can look at their AndroidManifest.xml to find the permissions. This is complicated by shared user IDs, which allow multiple applications to run as the same uid, and thus share permissions; any such app requesting a permission grants that permission to all such apps. The settings app, which has the Manage Applications UI, uses the "system" shared user ID so gets all such permissions available to the system.
Generally speaking, you find out what permissions you need because they are referenced from APIs you want to use, normally in the docs, occasionally by exceptions.
Conversely, if a given permission is not cited in the docs, except where it is named (e.g., on Manifest.permission), then it is probably a system permission that you are ineligible to hold unless you are working on alternative firmware.
And, if you are working on alternative firmware, you'd be well-versed in searching the source code. I use Google Code Search, personally, such as this search for your desired permission.
So, in the case of GET_PACKAGE_SIZE, the only place you find it in the docs is in Manifest.permission, and the only place the source code requires it is in a non-SDK method, so I suspect you cannot hold it.
Just check the docs.
GET_PACKAGE_SIZE : Allows an
application to find out the space used
by any package.
http://developer.android.com/reference/android/Manifest.permission.html
EDIT
I may have mis-understood the question. If you want to know what code is using a permission value. Then you will in fact have to dig through the source yourself.