So I'm testing out a bit of SAF and I'm finding the behaviour of the permissions on the URI to be pretty inconsistent.
Here's how I'm doing it:
I first obtain the tree URI using ACTION_OPEN_DOCUMENT_TREE and using the FLAG_GRANT_WRITE_URI_PERMISSION, FLAG_GRANT_READ_URI_PERMISSION and FLAG_GRANT_PERSISTABLE_URI_PERMISSION when calling on the intent.
In the OnActivityResult, I'm also using the
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION) to obtain persistence on the obtained uri.
I then store this uri in the sharedpreferences by uri.toString().
Now the issue I'm facing is that when I read the stringified uri back from the shared preferences and parse it to an URI and then try
getContentResolver().getPersistedUriPermissions(), the permissions I get from there are random. Sometimes the list is empty, sometimes it is having the right one. So my question boils down to how long will the permissions really persist? I could not find any documentation on this.
I'm trying this on an emulator. Sometimes I have the permissions even after reboot, but sometimes I lose them after a long idle time on the emulator(without any application running).
Please let me know if I'm missing something.
Thanks in advance.
Related
I'm persisting an URI obtained via SAF launched with the following intent:
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
.setType("application/json")
.putExtra("android.content.extra.SHOW_ADVANCED", true)
.putExtra("android.content.extra.FANCY", true)
.putExtra("android.content.extra.SHOW_FILESIZE", true)
.addCategory(Intent.CATEGORY_OPENABLE)
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
I didn't use the Intent.FLAG_GRANT_READ_URI_PERMISSION flag, because I don't need it. This initially works, and keeps working after activity is destroyed, at least for a few days. After a few days:
The URI saved to my database is apparently no longer valid. ContentResolver.persistedUriPermissions doesn't show it. Yet, the file hasn't moved and wasn't renamed.
ContentResolver.persistedUriPermissions does return a few URIs, but none can be resolved, despite being supposedly persisted. The error I get with contentResolver.openOutputStream(uri) is: "Failed to find provider info for com.google.android.apps.docs.storage" (as if it wasn't being found the day before!).
I have only tested with Google Drive for now, so I assumed the issue is related to it. I'm testing it with local storage, but I'll have to wait a few days to see the result. In the meantime: am I missing important flags on the Intent? Am I correct to save the URI as a string? Has this been known to happen to anyone else?
I'm running in to this issue too. It seems to be ok with Uris selected from the Files app, but the Google Drive Uris that I persist as Strings will stop working after an hour or so (a seemingly random delay, but after an app restart). On app startup, I could see that the Uri is still returned by getPersistedUriPermissions(), but when used in mediaMetadataRetriever.setDataSource(context, uri) it would throw an IllegalArgumentException. After this, it would not be returned by getPersistedUriPermissions() anymore, and if I try to access it again, it would throw a java.lang.RuntimeException: setDataSource failed: status = 0xFFFFFFEA.
I am using StorageVolume API to retrieve URI for both SDCard and USBStorage by following
StorageManager storageManager = (StorageManager) getSystemService(STORAGE_SERVICE);
for (StorageVolume storageVolume : storageManager.getStorageVolumes())
{
if (!storageVolume.isPrimary())
{
Intent accessIntent = storageVolume.createAccessIntent(null);
startActivityForResult(accessIntent, 11);
}
}
In onActivityResult I am able to retrieve the URI, saving it to the SharedPreferences and taking Persisting Permission. The thing is I am not able to distinguish the SDCard's URI from USBStorage's URI. It also applies for the URI's which are returned from Storage Access Framework. How could I really know that the user has really selected the expected storage not the wrong one.
Things Tried
I tried to send some data with the createAccessIntent but wasn't
able to retrieve values in onActivityResult. Just wanted to know
that why it doesn't have the extras that I put to the Intent returned by createAccessIntent.
I tried to context.getExternalFilesDirs(null)[] but it returns null for the SDCard item. I also checked the state of SDCard which was mounted. Obviously, I took runtime permission for storage from user.
Tried to assume that storageManager.getStorageVolumes() would return list of all the external storage with order of USBStorage at the first place as it does on my device but doesn't on all devices. It lists items randomly.
I also tried to use hack something like Environment.getRootDirectory() and get its parent and navigate to the SDCard on the root folder but it only returns the child elements of the mount point of SDCard and USBStorage on Marshmellow not beyond that. I mean it simply returns null on Nougat+ devices.
I've created a method that seems to work for this. It reads the proc/mounts file line-by-line until it finds one that contains both the provided StorageVolume's UUID and the string /dev/block/vold/public:179. In my testing, it seems that /dev/block/vold/public:179 indicates an SD card, whereas /dev/block/vold/public:8 indicates a USB drive.
I know nothing about Linux, so I'd be curious if anyone could confirm or deny the veracity of this. But it works across all 6 of my devices that have SD card slots.
https://gist.github.com/gavingt/2eb239d5df4e56290c1c65f25b8db8bf
As for getting the UUID from a Uri, that part is trivial.
I'm trying to provide multiple URIs via an Intent with the help of FileProvider from one app to another.
I got it working, when sending only one URI like the following:
resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
resultIntent.setDataAndType(contentUri,getContentResolver().getType(contentUri));
setResult(RESULT_OK, resultIntent);
In my "receiving" app I can continue like this in onActivityResult:
Uri returnUri = data.getData();
ParcelFileDescriptor mInputPFD = getContext().getContentResolver().openFileDescriptor(returnUri, "r");
Now I tried sending an ArrayList of URIs:
resultIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList);
resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
setResult(RESULT_OK, resultIntent);
but the granted permission flag only applies to the URI set in setData(), so I can't access the URIs from the ArrayList in my receiving app. I read about ClipData as a solution, but I'm unfortunatley forced to go with Min SDK 15.
So my question is, is it a good idea to set the permissions manually in my receiving app with something like
context.grantUriPermission("com.example.provider", returnUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
for every URI i would get if I chose the "ArrayList" option and revoke it later on? Can I do this at all? Couldn't every other app access the provider app's private files like that also?
And what package do I have to specify here? My provider app or my receiver app package, since I'm not getting this to work and only get some exception about missing permission.
Any help or hints are appreciated
Solved it.
My problem was, that I called grantUriPermission() in my receiver app and not in the provider app as it should be. So calling the following fixed the permission exceptions:
getApplicationContext().grantUriPermission(getCallingPackage(), fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
I'm little bit puzzled about grantUriPermission function. I'd like to use it to grant file access to another application via a FileProvider. Since FileProvider must be local (non-exported) for good reason, I must grant the other app temporary read/write permission. Documentation says:
Normally you should use Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION with the Intent being used to
start an activity instead of this function directly. If you use this
function directly, you should be sure to call revokeUriPermission(Uri,
int) when the target should no longer be allowed to access it.
Fair enough, since I don't need to open an activity nor explicitly start a service (the other app would just ask for that file when its time comes), I though I'm fine with just plain:
Uri newFileUri = FileProvider.getUriForFile(this, AUTHORITY, file);
grantUriPermission(OTHER_APP_PACKAGE_NAME, newFileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
To my surprise, I was still getting the security exception:
java.lang.SecurityException: Permission Denial: opening provider android.support.v4.content.FileProvider from ProcessRecord{42ee0b98 5878:example.com.myapplication3/u0a82} (pid=5878, uid=10082) that is not exported from uid 10081
After few experiments I found out that I must call the other app via an Intent at least once:
Intent intent = new Intent();
intent.setClassName(OTHER_APP_PACKAGE_NAME, OTHER_APP_SERVICE_NAME);
startService(intent);
As soon as I do this, I can do everything as expected -- I can remove this intent code, I can even reinstall both apps and it still works (!). It seems like there's a hidden requirement that the owner must call the other app at least once and its stored somewhere in the system.
Is this documented somewhere?
EDITED: first I thought I have to use Intent.FLAG_GRANT_READ_URI_PERMISSION flag, too but this seems to be not necessary.
I want my Android program to be able to save an SMS on the user's inbox.
I already found this question which does exactly that, but this statement:
getContentResolver().insert(Uri.parse("content://sms/sent"), values);
throws a SecurityException.
I already requested the SMS_WRITE permission in the manifest file.
Any ideas?
Took me some time too, seems like you also need the READ_SMS permission
(found it there: http://www.andreabaccega.com/blog/2010/08/12/write-an-sms-without-sending-it-on-android-2-2-froyo/)
worked for me.
Because the original SMS application uses this ContentResolver, you can retrieve it but as CW said, it may or may not be there. However; from my experience most applications that deal with SMS and interaction actually read the SMS ContentProvider (com.android.providers.telephony).
The permission name is WRITE_SMS... maybe that is the problem.