Reputation: 22173
I've got an app A that sends an intent to app B. App A has a file provider and share a file to B. It add the grants to the intent and it starts a service on B. B has an IntentService and process the intent. Now I want to change B to use a JobIntentService according to the new Android O policy. However I can't call startService from A, so I modified my code to send an explicit broadcast instead. I added the uri in the clipdata. However when I receive the intent on app B, I've got a security exception.
App A:
Uri contentUri = FileProvider.getUriForFile(this, "com.myotherapp.fileprovider",
newFile);
Intent i = new Intent(INTENT_UPDATE);
ClipData clipData = ClipData.newRawUri(at.name(), contentUri);
i.setClipData(clipData);
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
i.setPackage("com.myapp");
sendBroadcast(i);
App B:
@Override
public void onReceive(Context arg0, Intent arg1) {
if (MyService.INTENT_UPDATE.equals(arg1.getAction())) {
MyService.enqueueWork(arg0, arg1);
}
}
Stack:
Process: com.myapp, PID: 7690
java.lang.RuntimeException: Unable to start receiver com.myapp.MyReceiver: java.lang.SecurityException: UID 10083 does not have permission to content://com.myotherapp.fileprovider/files/Q3hnMrF8BsOJeznpVLizkTB4H6LkI90T.csv [user 0]
at android.app.ActivityThread.handleReceiver(ActivityThread.java:3259)
at android.app.ActivityThread.-wrap17(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1677)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6540)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
Caused by: java.lang.SecurityException: UID 10083 does not have permission to content://com.myotherapp.fileprovider/files/Q3hnMrF8BsOJeznpVLizkTB4H6LkI90T.csv [user 0]
at android.os.Parcel.readException(Parcel.java:1948)
at android.os.Parcel.readException(Parcel.java:1894)
at android.app.job.IJobScheduler$Stub$Proxy.enqueue(IJobScheduler.java:211)
at android.app.JobSchedulerImpl.enqueue(JobSchedulerImpl.java:53)
at android.support.v4.app.JobIntentService$JobWorkEnqueuer.enqueueWork(JobIntentService.java:314)
at android.support.v4.app.JobIntentService.enqueueWork(JobIntentService.java:472)
at com.myapp.MyService.enqueueWork(MyService.java:41)
at com.myapp.myrec.onReceive(UpdateReceiver.java:21)
at android.app.ActivityThread.handleReceiver(ActivityThread.java:3252)
at android.app.ActivityThread.-wrap17(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1677)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6540)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
Edit: I tried using a JobService using setClipData on a JobInfo. It doesn't work at all anyway.
Upvotes: 0
Views: 1883
Reputation: 8585
In the docs of FileProvider
they say that
A content URI allows you to grant read and write access using temporary access permissions. When you create an Intent containing a content URI, in order to send the content URI to a client app, you can also call Intent.setFlags() to add permissions. These permissions are available to the client app for as long as the stack for a receiving Activity is active. For an Intent going to a Service, the permissions are available as long as the Service is running.
My guess is that the permission is gone when you exit the broadcast receiver's onReceive
method.
Solution: So turned out that the problem is that Broadcast Receiver is not supported, and you can target only activities and services, as mentioned in the docs.
Upvotes: 1
Reputation: 22173
Ok, I found the problem. The broadcast receiver doesn't receive the permissions and you can't call startService
so you have only a tricky way to use a Job service in this case. I created a no display activity to forward the intent:
public class ProxyActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyService.enqueueWork(this, getIntent());
finish();
}
}
However I'm not totally sure of this solution because the activity can dispatch only one intent per-time. If you have multiples intents is a problem. You could use a launchMode with single instance and use onHandleIntent(), a good idea but you can't use the theme no display anymore and you don't know exactly when you can call finish().
Edit: As explained by Ian Lake (Google) "...as you've found, URI permissions can indeed be chained to other components by passing through the ClipData." and clip data are automatically created by the system when you use setData() or setDataAndType() on Api 16+, so this explains why it works, however the problem of a single intent is still there.
Edit2: I found a better solution at least in my case, use a brodcast receiver but the caller uses context.grantUriPermissions()
method.
Upvotes: 1