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);
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.
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.
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.
In one of my apps I'm using the following code to issue a phone call:
Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse(...));
startActivity(intent);
The docs say I do need the following Manifest permission to do so:
<uses-permission android:name="android.permission.CALL_PHONE" />
Is this really required? I do not understand the difference between a phone and a camera feature. When using a phone intent I do need a permission but I don't need permission for a camera intent:
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
...
startActivityForResult(intent, 1);
Is there a list on hardware features that need a permission if fired with the help of an intent and those that don't?
Actually, if you wish to just open the dialer with a specific phone number, without direct calling (needs user confirmation), you can do it without any permission:
Uri uri = Uri.parse("tel:" + PHONE_NUMBER);
Intent callIntent = new Intent(Intent.ACTION_DIAL, uri);
try {
context.startActivity(callIntent);
} catch (ActivityNotFoundException activityNotFoundException) {
// TODO: place code to handle users that have no call application installed, otherwise the app crashes
}
Is this really required?
Yes.
I do not understand the difference between a phone and a camera feature.
Phone calls can cost people money. Hence, if you directly place a phone call (vs. ACTION_DIAL to just put the number in the dialer), Android wants the user to agree ahead of time to allow this.
Taking pictures with the camera does not usually directly cost users any money.
Is there a list on hardware features that need a permission if fired with the help of an intent and those that don't?
Not really.
When you issue a request to the camera, it merely opens an app requiring user interaction before it can do anything.
Phone calls open an app with the phone number already entered so you merely just have to press a button.
There's a much higher risk that you'll accidentally call someone than if you were to accidentally take a picture (Which you could just delete if taken accidentally.)
I have an application say A that have all the permissions enabled at installation. Another app Say B don't have a permission and want to get that permission. Can B communicate with A, So that A can transfer its permission to B.
PLS reply, I'm stuck here. I want to get some permissions dynamically. Is this the best idea or any other idea?
As far as I know, apps can't necessarily give permissions to other apps, BUT AppB could inherit permissions from AppA IF you are the developer of both apps. If both AppA and AppB declare the same sharedUserId value in their manifest (android:sharedUserId="xyz") AND both AppA and AppB are signed with the same signature, then Android will consider them to be the same app as far as permissions go. So, AppB could exist on the device without permission "perm1" for example. Then, AppA could be installed with "perm1". IF AppA and AppB have the same sharedUserId and signature then, when AppA is installed, AppB will be "granted" "perm1".
I haven't tested this just now, but I know it used to work (as of a year ago or so).
That would be quite in-secure, don't your think, if an application could give any permissions to another application...
Some evil-doer would just have to convince you to install his A application ; and, then, no matter what other B application you'd install, that B application wouldn't have to request any specific permission at installation (those would later be granted by A) -- and B would still be able to do anything on your device ?
I sure hope what you're asking is not possible ;-)
Your application A can provide some Content Providers to access information. Application B could use the content provider of A to gain the information. http://developer.android.com/guide/topics/providers/content-providers.html
But somehow this sounds like you want to do something evil. If you like to have more information please provide more about your need to do that!
Yes this is possible in a roundabout way using PendingIntents. This is not an exact code snippet but should give you the idea:
You cannot transfer the permissions, but you can transfer capabilities to perform certain actions from A to B. Let's say you want to transfer the capability of executing a certain ACTION.
A needs to create a pending intent:
Intent intent = new Intent(ACTION);
PendingIntent pIntent = PendingIntent.getActivity(context, requestCode, intent, flags);
A sends this to B by marshalling the pending intent
Intent intent = new Intent(context, B.class);
intent.putExtra("pendingIntent", pIntent);
startActivity(intent);
At B we deserialize the pending intent and we can use it to perform the restricted ACTION
PendingIntent pIntent = intent.getExtra("pendingIntent");
pIntent.send();