Implement issue #36590595: Add ability to associated a ClipData with JobInfo
Yum!
Also needed to have a Context.revokeUriPermission() variant that is sane,
so reasonable CTS tests can be written.
Test: new ClipDataJobTest added.
Change-Id: Ia3135ea788a6e32c971bae7dab3a844d0ef4139c
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 467ba99..5a7246a 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1790,7 +1790,18 @@
public void revokeUriPermission(Uri uri, int modeFlags) {
try {
ActivityManager.getService().revokeUriPermission(
- mMainThread.getApplicationThread(),
+ mMainThread.getApplicationThread(), null,
+ ContentProvider.getUriWithoutUserId(uri), modeFlags, resolveUserId(uri));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void revokeUriPermission(String targetPackage, Uri uri, int modeFlags) {
+ try {
+ ActivityManager.getService().revokeUriPermission(
+ mMainThread.getApplicationThread(), targetPackage,
ContentProvider.getUriWithoutUserId(uri), modeFlags, resolveUserId(uri));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 0a5e4be..f4d26fd 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -172,7 +172,8 @@
in IBinder callerToken);
void grantUriPermission(in IApplicationThread caller, in String targetPkg, in Uri uri,
int mode, int userId);
- void revokeUriPermission(in IApplicationThread caller, in Uri uri, int mode, int userId);
+ void revokeUriPermission(in IApplicationThread caller, in String targetPkg, in Uri uri,
+ int mode, int userId);
void setActivityController(in IActivityController watcher, boolean imAMonkey);
void showWaitingForDebugger(in IApplicationThread who, boolean waiting);
/*
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index 78e4c0d..96eb0ea 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ClipData;
import android.content.ComponentName;
import android.net.Uri;
import android.os.Bundle;
@@ -197,6 +198,8 @@
private final int jobId;
private final PersistableBundle extras;
private final Bundle transientExtras;
+ private final ClipData clipData;
+ private final int clipGrantFlags;
private final ComponentName service;
private final int constraintFlags;
private final TriggerContentUri[] triggerContentUris;
@@ -240,6 +243,21 @@
}
/**
+ * ClipData of information that is returned to your application at execution time,
+ * but not persisted by the system.
+ */
+ public ClipData getClipData() {
+ return clipData;
+ }
+
+ /**
+ * Permission grants that go along with {@link #getClipData}.
+ */
+ public int getClipGrantFlags() {
+ return clipGrantFlags;
+ }
+
+ /**
* Name of the service endpoint that will be called back into by the JobScheduler.
*/
public ComponentName getService() {
@@ -415,6 +433,13 @@
jobId = in.readInt();
extras = in.readPersistableBundle();
transientExtras = in.readBundle();
+ if (in.readInt() != 0) {
+ clipData = ClipData.CREATOR.createFromParcel(in);
+ clipGrantFlags = in.readInt();
+ } else {
+ clipData = null;
+ clipGrantFlags = 0;
+ }
service = in.readParcelable(null);
constraintFlags = in.readInt();
triggerContentUris = in.createTypedArray(TriggerContentUri.CREATOR);
@@ -439,6 +464,8 @@
jobId = b.mJobId;
extras = b.mExtras.deepCopy();
transientExtras = b.mTransientExtras.deepCopy();
+ clipData = b.mClipData;
+ clipGrantFlags = b.mClipGrantFlags;
service = b.mJobService;
constraintFlags = b.mConstraintFlags;
triggerContentUris = b.mTriggerContentUris != null
@@ -471,6 +498,13 @@
out.writeInt(jobId);
out.writePersistableBundle(extras);
out.writeBundle(transientExtras);
+ if (clipData != null) {
+ out.writeInt(1);
+ clipData.writeToParcel(out, flags);
+ out.writeInt(clipGrantFlags);
+ } else {
+ out.writeInt(0);
+ }
out.writeParcelable(service, flags);
out.writeInt(constraintFlags);
out.writeTypedArray(triggerContentUris, flags);
@@ -597,6 +631,8 @@
private final ComponentName mJobService;
private PersistableBundle mExtras = PersistableBundle.EMPTY;
private Bundle mTransientExtras = Bundle.EMPTY;
+ private ClipData mClipData;
+ private int mClipGrantFlags;
private int mPriority = PRIORITY_DEFAULT;
private int mFlags;
// Requirements.
@@ -669,6 +705,34 @@
}
/**
+ * Set a {@link ClipData} associated with this Job.
+ *
+ * <p>The main purpose of providing a ClipData is to allow granting of
+ * URI permissions for data associated with the clip. The exact kind
+ * of permission grant to perform is specified through <var>grantFlags</var>.
+ *
+ * <p>If the ClipData contains items that are Intents, any
+ * grant flags in those Intents will be ignored. Only flags provided as an argument
+ * to this method are respected, and will be applied to all Uri or
+ * Intent items in the clip (or sub-items of the clip).
+ *
+ * <p>Because setting this property is not compatible with persisted
+ * jobs, doing so will throw an {@link java.lang.IllegalArgumentException} when
+ * {@link android.app.job.JobInfo.Builder#build()} is called.</p>
+ *
+ * @param clip The new clip to set. May be null to clear the current clip.
+ * @param grantFlags The desired permissions to grant for any URIs. This should be
+ * a combination of {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION},
+ * {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION}, and
+ * {@link android.content.Intent#FLAG_GRANT_PREFIX_URI_PERMISSION}.
+ */
+ public Builder setClipData(ClipData clip, int grantFlags) {
+ mClipData = clip;
+ mClipGrantFlags = grantFlags;
+ return this;
+ }
+
+ /**
* Set some description of the kind of network type your job needs to have.
* Not calling this function means the network is not necessary, as the default is
* {@link #NETWORK_TYPE_NONE}.
@@ -892,25 +956,33 @@
"constraints, this is not allowed.");
}
// Check that a deadline was not set on a periodic job.
- if (mIsPeriodic && (mMaxExecutionDelayMillis != 0L)) {
- throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " +
- "periodic job.");
+ if (mIsPeriodic) {
+ if (mMaxExecutionDelayMillis != 0L) {
+ throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " +
+ "periodic job.");
+ }
+ if (mMinLatencyMillis != 0L) {
+ throw new IllegalArgumentException("Can't call setMinimumLatency() on a " +
+ "periodic job");
+ }
+ if (mTriggerContentUris != null) {
+ throw new IllegalArgumentException("Can't call addTriggerContentUri() on a " +
+ "periodic job");
+ }
}
- if (mIsPeriodic && (mMinLatencyMillis != 0L)) {
- throw new IllegalArgumentException("Can't call setMinimumLatency() on a " +
- "periodic job");
- }
- if (mIsPeriodic && (mTriggerContentUris != null)) {
- throw new IllegalArgumentException("Can't call addTriggerContentUri() on a " +
- "periodic job");
- }
- if (mIsPersisted && (mTriggerContentUris != null)) {
- throw new IllegalArgumentException("Can't call addTriggerContentUri() on a " +
- "persisted job");
- }
- if (mIsPersisted && !mTransientExtras.isEmpty()) {
- throw new IllegalArgumentException("Can't call setTransientExtras() on a " +
- "persisted job");
+ if (mIsPersisted) {
+ if (mTriggerContentUris != null) {
+ throw new IllegalArgumentException("Can't call addTriggerContentUri() on a " +
+ "persisted job");
+ }
+ if (!mTransientExtras.isEmpty()) {
+ throw new IllegalArgumentException("Can't call setTransientExtras() on a " +
+ "persisted job");
+ }
+ if (mClipData != null) {
+ throw new IllegalArgumentException("Can't call setClipData() on a " +
+ "persisted job");
+ }
}
if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
throw new IllegalArgumentException("An idle mode job will not respect any" +
diff --git a/core/java/android/app/job/JobParameters.java b/core/java/android/app/job/JobParameters.java
index ba168b7..8d52d3b 100644
--- a/core/java/android/app/job/JobParameters.java
+++ b/core/java/android/app/job/JobParameters.java
@@ -17,6 +17,7 @@
package android.app.job;
import android.app.job.IJobCallback;
+import android.content.ClipData;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
@@ -44,6 +45,8 @@
private final int jobId;
private final PersistableBundle extras;
private final Bundle transientExtras;
+ private final ClipData clipData;
+ private final int clipGrantFlags;
private final IBinder callback;
private final boolean overrideDeadlineExpired;
private final Uri[] mTriggeredContentUris;
@@ -53,11 +56,14 @@
/** @hide */
public JobParameters(IBinder callback, int jobId, PersistableBundle extras,
- Bundle transientExtras, boolean overrideDeadlineExpired, Uri[] triggeredContentUris,
+ Bundle transientExtras, ClipData clipData, int clipGrantFlags,
+ boolean overrideDeadlineExpired, Uri[] triggeredContentUris,
String[] triggeredContentAuthorities) {
this.jobId = jobId;
this.extras = extras;
this.transientExtras = transientExtras;
+ this.clipData = clipData;
+ this.clipGrantFlags = clipGrantFlags;
this.callback = callback;
this.overrideDeadlineExpired = overrideDeadlineExpired;
this.mTriggeredContentUris = triggeredContentUris;
@@ -98,6 +104,24 @@
}
/**
+ * @return The clip you passed in when constructing this job with
+ * {@link android.app.job.JobInfo.Builder#setClipData(ClipData, int)}. Will be null
+ * if it was not set.
+ */
+ public ClipData getClipData() {
+ return clipData;
+ }
+
+ /**
+ * @return The clip grant flags you passed in when constructing this job with
+ * {@link android.app.job.JobInfo.Builder#setClipData(ClipData, int)}. Will be 0
+ * if it was not set.
+ */
+ public int getClipGrantFlags() {
+ return clipGrantFlags;
+ }
+
+ /**
* For jobs with {@link android.app.job.JobInfo.Builder#setOverrideDeadline(long)} set, this
* provides an easy way to tell whether the job is being executed due to the deadline
* expiring. Note: If the job is running because its deadline expired, it implies that its
@@ -140,6 +164,13 @@
jobId = in.readInt();
extras = in.readPersistableBundle();
transientExtras = in.readBundle();
+ if (in.readInt() != 0) {
+ clipData = ClipData.CREATOR.createFromParcel(in);
+ clipGrantFlags = in.readInt();
+ } else {
+ clipData = null;
+ clipGrantFlags = 0;
+ }
callback = in.readStrongBinder();
overrideDeadlineExpired = in.readInt() == 1;
mTriggeredContentUris = in.createTypedArray(Uri.CREATOR);
@@ -162,6 +193,13 @@
dest.writeInt(jobId);
dest.writePersistableBundle(extras);
dest.writeBundle(transientExtras);
+ if (clipData != null) {
+ dest.writeInt(1);
+ clipData.writeToParcel(dest, flags);
+ dest.writeInt(clipGrantFlags);
+ } else {
+ dest.writeInt(0);
+ }
dest.writeStrongBinder(callback);
dest.writeInt(overrideDeadlineExpired ? 1 : 0);
dest.writeTypedArray(mTriggeredContentUris, flags);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 1803bbe..dbbfe30 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4078,8 +4078,8 @@
/**
* Remove all permissions to access a particular content provider Uri
- * that were previously added with {@link #grantUriPermission}. The given
- * Uri will match all previously granted Uris that are the same or a
+ * that were previously added with {@link #grantUriPermission} or <em>any other</em> mechanism.
+ * The given Uri will match all previously granted Uris that are the same or a
* sub-path of the given Uri. That is, revoking "content://foo/target" will
* revoke both "content://foo/target" and "content://foo/target/sub", but not
* "content://foo". It will not remove any prefix grants that exist at a
@@ -4089,10 +4089,16 @@
* regular permission access to a Uri, but had received access to it through
* a specific Uri permission grant, you could not revoke that grant with this
* function and a {@link SecurityException} would be thrown. As of
- * {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this function will not throw a security exception,
- * but will remove whatever permission grants to the Uri had been given to the app
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this function will not throw a security
+ * exception, but will remove whatever permission grants to the Uri had been given to the app
* (or none).</p>
*
+ * <p>Unlike {@link #revokeUriPermission(String, Uri, int)}, this method impacts all permission
+ * grants matching the given Uri, for any package they had been granted to, through any
+ * mechanism this had happened (such as indirectly through the clipboard, activity launch,
+ * service start, etc). That means this can be potentially dangerous to use, as it can
+ * revoke grants that another app could be strongly expecting to stick around.</p>
+ *
* @param uri The Uri you would like to revoke access to.
* @param modeFlags The desired access modes. Any combination of
* {@link Intent#FLAG_GRANT_READ_URI_PERMISSION
@@ -4105,6 +4111,34 @@
public abstract void revokeUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags);
/**
+ * Remove permissions to access a particular content provider Uri
+ * that were previously added with {@link #grantUriPermission} for a specific target
+ * package. The given Uri will match all previously granted Uris that are the same or a
+ * sub-path of the given Uri. That is, revoking "content://foo/target" will
+ * revoke both "content://foo/target" and "content://foo/target/sub", but not
+ * "content://foo". It will not remove any prefix grants that exist at a
+ * higher level.
+ *
+ * <p>Unlike {@link #revokeUriPermission(Uri, int)}, this method will <em>only</em>
+ * revoke permissions that had been explicitly granted through {@link #grantUriPermission}
+ * and only for the package specified. Any matching grants that have happened through
+ * other mechanisms (clipboard, activity launching, service starting, etc) will not be
+ * removed.</p>
+ *
+ * @param toPackage The package you had previously granted access to.
+ * @param uri The Uri you would like to revoke access to.
+ * @param modeFlags The desired access modes. Any combination of
+ * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION
+ * Intent.FLAG_GRANT_READ_URI_PERMISSION} or
+ * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION
+ * Intent.FLAG_GRANT_WRITE_URI_PERMISSION}.
+ *
+ * @see #grantUriPermission
+ */
+ public abstract void revokeUriPermission(String toPackage, Uri uri,
+ @Intent.AccessUriMode int modeFlags);
+
+ /**
* Determine whether a particular process and user ID has been granted
* permission to access a specific URI. This only checks for permissions
* that have been explicitly granted -- if the given process/uid has
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 75784a6..53b021c 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -781,6 +781,11 @@
}
@Override
+ public void revokeUriPermission(String targetPackage, Uri uri, int modeFlags) {
+ mBase.revokeUriPermission(targetPackage, uri, modeFlags);
+ }
+
+ @Override
public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
return mBase.checkUriPermission(uri, pid, uid, modeFlags);
}
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index e82fe03..6f388e2 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -311,6 +311,20 @@
}
/**
+ * @hide this should probably be the implementation of isEmpty(). To do that we
+ * need to ensure we always use the special empty parcel form when the bundle is
+ * empty. (This may already be the case, but to be safe we'll do this later when
+ * we aren't trying to stabilize.)
+ */
+ public boolean maybeIsEmpty() {
+ if (isParcelled()) {
+ return isEmptyParcel();
+ } else {
+ return isEmpty();
+ }
+ }
+
+ /**
* Removes all elements from the mapping of this Bundle.
*/
public void clear() {
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index c1292e7..9b5ff29 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -1209,4 +1209,18 @@
}
return "Bundle[" + mMap.toString() + "]";
}
+
+ /**
+ * @hide
+ */
+ public synchronized String toShortString() {
+ if (mParcelledData != null) {
+ if (isEmptyParcel()) {
+ return "EMPTY_PARCEL";
+ } else {
+ return "mParcelledData.dataSize=" + mParcelledData.dataSize();
+ }
+ }
+ return mMap.toString();
+ }
}
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
index 75f9c11..3ed5b17 100644
--- a/core/java/android/os/PersistableBundle.java
+++ b/core/java/android/os/PersistableBundle.java
@@ -309,4 +309,16 @@
}
return "PersistableBundle[" + mMap.toString() + "]";
}
+
+ /** @hide */
+ synchronized public String toShortString() {
+ if (mParcelledData != null) {
+ if (isEmptyParcel()) {
+ return "EMPTY_PARCEL";
+ } else {
+ return "mParcelledData.dataSize=" + mParcelledData.dataSize();
+ }
+ }
+ return mMap.toString();
+ }
}