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/api/current.txt b/api/current.txt
index ec2a0e6..e2f8e3a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6740,6 +6740,8 @@
   public class JobInfo implements android.os.Parcelable {
     method public int describeContents();
     method public int getBackoffPolicy();
+    method public android.content.ClipData getClipData();
+    method public int getClipGrantFlags();
     method public android.os.PersistableBundle getExtras();
     method public long getFlexMillis();
     method public int getId();
@@ -6778,6 +6780,7 @@
     method public android.app.job.JobInfo.Builder addTriggerContentUri(android.app.job.JobInfo.TriggerContentUri);
     method public android.app.job.JobInfo build();
     method public android.app.job.JobInfo.Builder setBackoffCriteria(long, int);
+    method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int);
     method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
     method public android.app.job.JobInfo.Builder setMinimumLatency(long);
     method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
@@ -6806,6 +6809,8 @@
 
   public class JobParameters implements android.os.Parcelable {
     method public int describeContents();
+    method public android.content.ClipData getClipData();
+    method public int getClipGrantFlags();
     method public android.os.PersistableBundle getExtras();
     method public int getJobId();
     method public android.os.Bundle getTransientExtras();
@@ -8841,6 +8846,7 @@
     method public abstract deprecated void removeStickyBroadcast(android.content.Intent);
     method public abstract deprecated void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
     method public abstract void revokeUriPermission(android.net.Uri, int);
+    method public abstract void revokeUriPermission(java.lang.String, android.net.Uri, int);
     method public abstract void sendBroadcast(android.content.Intent);
     method public abstract void sendBroadcast(android.content.Intent, java.lang.String);
     method public abstract void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle);
@@ -9033,6 +9039,7 @@
     method public deprecated void removeStickyBroadcast(android.content.Intent);
     method public deprecated void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
     method public void revokeUriPermission(android.net.Uri, int);
+    method public void revokeUriPermission(java.lang.String, android.net.Uri, int);
     method public void sendBroadcast(android.content.Intent);
     method public void sendBroadcast(android.content.Intent, java.lang.String);
     method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle);
@@ -40664,6 +40671,7 @@
     method public void removeStickyBroadcast(android.content.Intent);
     method public void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
     method public void revokeUriPermission(android.net.Uri, int);
+    method public void revokeUriPermission(java.lang.String, android.net.Uri, int);
     method public void sendBroadcast(android.content.Intent);
     method public void sendBroadcast(android.content.Intent, java.lang.String);
     method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle);
diff --git a/api/system-current.txt b/api/system-current.txt
index 2890041..1f37f9b 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -7167,6 +7167,8 @@
   public class JobInfo implements android.os.Parcelable {
     method public int describeContents();
     method public int getBackoffPolicy();
+    method public android.content.ClipData getClipData();
+    method public int getClipGrantFlags();
     method public android.os.PersistableBundle getExtras();
     method public long getFlexMillis();
     method public int getId();
@@ -7205,6 +7207,7 @@
     method public android.app.job.JobInfo.Builder addTriggerContentUri(android.app.job.JobInfo.TriggerContentUri);
     method public android.app.job.JobInfo build();
     method public android.app.job.JobInfo.Builder setBackoffCriteria(long, int);
+    method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int);
     method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
     method public android.app.job.JobInfo.Builder setMinimumLatency(long);
     method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
@@ -7233,6 +7236,8 @@
 
   public class JobParameters implements android.os.Parcelable {
     method public int describeContents();
+    method public android.content.ClipData getClipData();
+    method public int getClipGrantFlags();
     method public android.os.PersistableBundle getExtras();
     method public int getJobId();
     method public android.os.Bundle getTransientExtras();
@@ -9333,6 +9338,7 @@
     method public abstract deprecated void removeStickyBroadcast(android.content.Intent);
     method public abstract deprecated void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
     method public abstract void revokeUriPermission(android.net.Uri, int);
+    method public abstract void revokeUriPermission(java.lang.String, android.net.Uri, int);
     method public abstract void sendBroadcast(android.content.Intent);
     method public abstract void sendBroadcast(android.content.Intent, java.lang.String);
     method public abstract void sendBroadcast(android.content.Intent, java.lang.String, android.os.Bundle);
@@ -9539,6 +9545,7 @@
     method public deprecated void removeStickyBroadcast(android.content.Intent);
     method public deprecated void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
     method public void revokeUriPermission(android.net.Uri, int);
+    method public void revokeUriPermission(java.lang.String, android.net.Uri, int);
     method public void sendBroadcast(android.content.Intent);
     method public void sendBroadcast(android.content.Intent, java.lang.String);
     method public void sendBroadcast(android.content.Intent, java.lang.String, android.os.Bundle);
@@ -44099,6 +44106,7 @@
     method public void removeStickyBroadcast(android.content.Intent);
     method public void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
     method public void revokeUriPermission(android.net.Uri, int);
+    method public void revokeUriPermission(java.lang.String, android.net.Uri, int);
     method public void sendBroadcast(android.content.Intent);
     method public void sendBroadcast(android.content.Intent, java.lang.String);
     method public void sendBroadcast(android.content.Intent, java.lang.String, android.os.Bundle);
diff --git a/api/test-current.txt b/api/test-current.txt
index 69ea620..c2fb7e1 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6769,6 +6769,8 @@
   public class JobInfo implements android.os.Parcelable {
     method public int describeContents();
     method public int getBackoffPolicy();
+    method public android.content.ClipData getClipData();
+    method public int getClipGrantFlags();
     method public android.os.PersistableBundle getExtras();
     method public long getFlexMillis();
     method public int getId();
@@ -6807,6 +6809,7 @@
     method public android.app.job.JobInfo.Builder addTriggerContentUri(android.app.job.JobInfo.TriggerContentUri);
     method public android.app.job.JobInfo build();
     method public android.app.job.JobInfo.Builder setBackoffCriteria(long, int);
+    method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int);
     method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
     method public android.app.job.JobInfo.Builder setMinimumLatency(long);
     method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
@@ -6835,6 +6838,8 @@
 
   public class JobParameters implements android.os.Parcelable {
     method public int describeContents();
+    method public android.content.ClipData getClipData();
+    method public int getClipGrantFlags();
     method public android.os.PersistableBundle getExtras();
     method public int getJobId();
     method public android.os.Bundle getTransientExtras();
@@ -8873,6 +8878,7 @@
     method public abstract deprecated void removeStickyBroadcast(android.content.Intent);
     method public abstract deprecated void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
     method public abstract void revokeUriPermission(android.net.Uri, int);
+    method public abstract void revokeUriPermission(java.lang.String, android.net.Uri, int);
     method public abstract void sendBroadcast(android.content.Intent);
     method public abstract void sendBroadcast(android.content.Intent, java.lang.String);
     method public abstract void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle);
@@ -9066,6 +9072,7 @@
     method public deprecated void removeStickyBroadcast(android.content.Intent);
     method public deprecated void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
     method public void revokeUriPermission(android.net.Uri, int);
+    method public void revokeUriPermission(java.lang.String, android.net.Uri, int);
     method public void sendBroadcast(android.content.Intent);
     method public void sendBroadcast(android.content.Intent, java.lang.String);
     method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle);
@@ -40868,6 +40875,7 @@
     method public void removeStickyBroadcast(android.content.Intent);
     method public void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
     method public void revokeUriPermission(android.net.Uri, int);
+    method public void revokeUriPermission(java.lang.String, android.net.Uri, int);
     method public void sendBroadcast(android.content.Intent);
     method public void sendBroadcast(android.content.Intent, java.lang.String);
     method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle);
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();
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b4da152..71ad674 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2386,7 +2386,7 @@
             } break;
             case DELETE_DUMPHEAP_MSG: {
                 revokeUriPermission(ActivityThread.currentActivityThread().getApplicationThread(),
-                        DumpHeapActivity.JAVA_URI,
+                        null, DumpHeapActivity.JAVA_URI,
                         Intent.FLAG_GRANT_READ_URI_PERMISSION
                                 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
                         UserHandle.myUserId());
@@ -8944,7 +8944,8 @@
         }
     }
 
-    private void revokeUriPermissionLocked(int callingUid, GrantUri grantUri, final int modeFlags) {
+    private void revokeUriPermissionLocked(String targetPackage, int callingUid, GrantUri grantUri,
+            final int modeFlags) {
         if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
                 "Revoking all granted permissions to " + grantUri);
 
@@ -8965,8 +8966,11 @@
             final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
             if (perms != null) {
                 boolean persistChanged = false;
-                for (Iterator<UriPermission> it = perms.values().iterator(); it.hasNext();) {
-                    final UriPermission perm = it.next();
+                for (int i = perms.size()-1; i >= 0; i--) {
+                    final UriPermission perm = perms.valueAt(i);
+                    if (targetPackage != null && !targetPackage.equals(perm.targetPkg)) {
+                        continue;
+                    }
                     if (perm.uri.sourceUserId == grantUri.sourceUserId
                             && perm.uri.uri.isPathPrefixMatch(grantUri.uri)) {
                         if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
@@ -8975,7 +8979,7 @@
                         persistChanged |= perm.revokeModes(
                                 modeFlags | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, false);
                         if (perm.modeFlags == 0) {
-                            it.remove();
+                            perms.removeAt(i);
                         }
                     }
                 }
@@ -8992,29 +8996,30 @@
         boolean persistChanged = false;
 
         // Go through all of the permissions and remove any that match.
-        int N = mGrantedUriPermissions.size();
-        for (int i = 0; i < N; i++) {
+        for (int i = mGrantedUriPermissions.size()-1; i >= 0; i--) {
             final int targetUid = mGrantedUriPermissions.keyAt(i);
             final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
 
-            for (Iterator<UriPermission> it = perms.values().iterator(); it.hasNext();) {
-                final UriPermission perm = it.next();
+            for (int j = perms.size()-1; j >= 0; j--) {
+                final UriPermission perm = perms.valueAt(j);
+                if (targetPackage != null && !targetPackage.equals(perm.targetPkg)) {
+                    continue;
+                }
                 if (perm.uri.sourceUserId == grantUri.sourceUserId
                         && perm.uri.uri.isPathPrefixMatch(grantUri.uri)) {
                     if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
                                 "Revoking " + perm.targetUid + " permission to " + perm.uri);
                     persistChanged |= perm.revokeModes(
-                            modeFlags | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, true);
+                            modeFlags | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION,
+                            targetPackage == null);
                     if (perm.modeFlags == 0) {
-                        it.remove();
+                        perms.removeAt(j);
                     }
                 }
             }
 
             if (perms.isEmpty()) {
-                mGrantedUriPermissions.remove(targetUid);
-                N--;
-                i--;
+                mGrantedUriPermissions.removeAt(i);
             }
         }
 
@@ -9028,8 +9033,8 @@
      * @param userId The userId in which the uri is to be resolved.
      */
     @Override
-    public void revokeUriPermission(IApplicationThread caller, Uri uri, final int modeFlags,
-            int userId) {
+    public void revokeUriPermission(IApplicationThread caller, String targetPackage, Uri uri,
+            final int modeFlags, int userId) {
         enforceNotIsolatedCaller("revokeUriPermission");
         synchronized(this) {
             final ProcessRecord r = getRecordForAppLocked(caller);
@@ -9056,7 +9061,8 @@
                 return;
             }
 
-            revokeUriPermissionLocked(r.uid, new GrantUri(userId, uri, false), modeFlags);
+            revokeUriPermissionLocked(targetPackage, r.uid, new GrantUri(userId, uri, false),
+                    modeFlags);
         }
     }
 
@@ -21164,7 +21170,7 @@
                         public void run() {
                             revokeUriPermission(ActivityThread.currentActivityThread()
                                             .getApplicationThread(),
-                                    DumpHeapActivity.JAVA_URI,
+                                    null, DumpHeapActivity.JAVA_URI,
                                     Intent.FLAG_GRANT_READ_URI_PERMISSION
                                             | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
                                     UserHandle.myUserId());
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 46d7bfc..2de9aae 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -616,6 +616,9 @@
                 }
             }
 
+            // This may throw a SecurityException.
+            jobStatus.prepare(ActivityManager.getService());
+
             toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
             if (toCancel != null) {
                 cancelJobImpl(toCancel, jobStatus);
@@ -712,6 +715,7 @@
 
     private void cancelJobImpl(JobStatus cancelled, JobStatus incomingJob) {
         if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
+        cancelled.unprepare(ActivityManager.getService());
         stopTrackingJob(cancelled, incomingJob, true /* writeBack */);
         synchronized (mLock) {
             // Remove from pending queue.
@@ -892,6 +896,9 @@
      */
     private void startTrackingJob(JobStatus jobStatus, JobStatus lastJob) {
         synchronized (mLock) {
+            if (!jobStatus.isPrepared()) {
+                Slog.wtf(TAG, "Not yet prepared when started tracking: " + jobStatus);
+            }
             final boolean update = mJobs.add(jobStatus);
             if (mReadyToRock) {
                 for (int i = 0; i < mControllers.size(); i++) {
@@ -1078,11 +1085,22 @@
         // that may cause ordering problems if the app removes jobStatus while in here.
         if (needsReschedule) {
             JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
+            try {
+                rescheduled.prepare(ActivityManager.getService());
+            } catch (SecurityException e) {
+                Slog.w(TAG, "Unable to regrant job permissions for " + rescheduled);
+            }
             startTrackingJob(rescheduled, jobStatus);
         } else if (jobStatus.getJob().isPeriodic()) {
             JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
+            try {
+                rescheduledPeriodic.prepare(ActivityManager.getService());
+            } catch (SecurityException e) {
+                Slog.w(TAG, "Unable to regrant job permissions for " + rescheduledPeriodic);
+            }
             startTrackingJob(rescheduledPeriodic, jobStatus);
         }
+        jobStatus.unprepare(ActivityManager.getService());
         reportActive();
         mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
     }
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 1337046..19b9888 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -17,6 +17,7 @@
 package com.android.server.job;
 
 import android.app.ActivityManager;
+import android.app.job.JobInfo;
 import android.app.job.JobParameters;
 import android.app.job.IJobCallback;
 import android.app.job.IJobService;
@@ -187,9 +188,10 @@
                 triggeredAuthorities = new String[job.changedAuthorities.size()];
                 job.changedAuthorities.toArray(triggeredAuthorities);
             }
-            mParams = new JobParameters(this, job.getJobId(), job.getExtras(),
-                    job.getTransientExtras(), isDeadlineExpired,
-                    triggeredUris, triggeredAuthorities);
+            final JobInfo ji = job.getJob();
+            mParams = new JobParameters(this, job.getJobId(), ji.getExtras(),
+                    ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
+                    isDeadlineExpired, triggeredUris, triggeredAuthorities);
             mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
 
             mVerb = VERB_BINDING;
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index ccfc287..c0264df 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -16,6 +16,8 @@
 
 package com.android.server.job;
 
+import android.app.ActivityManager;
+import android.app.IActivityManager;
 import android.content.ComponentName;
 import android.app.job.JobInfo;
 import android.content.Context;
@@ -294,7 +296,7 @@
                     addAttributesToJobTag(out, jobStatus);
                     writeConstraintsToXml(out, jobStatus);
                     writeExecutionCriteriaToXml(out, jobStatus);
-                    writeBundleToXml(jobStatus.getExtras(), out);
+                    writeBundleToXml(jobStatus.getJob().getExtras(), out);
                     out.endTag(null, "job");
                 }
                 out.endTag(null, "job-info");
@@ -449,8 +451,11 @@
                 synchronized (mLock) {
                     jobs = readJobMapImpl(fis);
                     if (jobs != null) {
+                        IActivityManager am = ActivityManager.getService();
                         for (int i=0; i<jobs.size(); i++) {
-                            this.jobSet.add(jobs.get(i));
+                            JobStatus js = jobs.get(i);
+                            js.prepare(am);
+                            this.jobSet.add(js);
                         }
                     }
                 }
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index ebb53a1..47630d0 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -17,19 +17,25 @@
 package com.android.server.job.controllers;
 
 import android.app.AppGlobals;
+import android.app.IActivityManager;
 import android.app.job.JobInfo;
+import android.content.ClipData;
 import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.Intent;
 import android.net.Uri;
-import android.os.Bundle;
-import android.os.PersistableBundle;
+import android.os.Binder;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.text.format.DateUtils;
 import android.util.ArraySet;
+import android.util.Slog;
 import android.util.TimeUtils;
 
 import java.io.PrintWriter;
+import java.util.Arrays;
 
 /**
  * Uniquely identifies a job internally.
@@ -43,6 +49,8 @@
  * @hide
  */
 public final class JobStatus {
+    static final String TAG = "JobSchedulerService";
+
     public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
     public static final long NO_EARLIEST_RUNTIME = 0L;
 
@@ -88,6 +96,9 @@
 
     final String tag;
 
+    private IBinder permissionOwner;
+    private boolean prepared;
+
     /**
      * Earliest point in the future at which this job will be eligible to run. A value of 0
      * indicates there is no delay constraint. See {@link #hasTimingDelayConstraint()}.
@@ -241,6 +252,93 @@
                 earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis);
     }
 
+    public void prepare(IActivityManager am) {
+        if (prepared) {
+            Slog.wtf(TAG, "Already prepared: " + this);
+            return;
+        }
+        prepared = true;
+        final ClipData clip = job.getClipData();
+        if (clip != null) {
+            final int N = clip.getItemCount();
+            for (int i = 0; i < N; i++) {
+                grantItemLocked(am, clip.getItemAt(i), sourceUid, sourcePackageName, sourceUserId);
+            }
+        }
+    }
+
+    public void unprepare(IActivityManager am) {
+        if (!prepared) {
+            Slog.wtf(TAG, "Hasn't been prepared: " + this);
+            return;
+        }
+        prepared = false;
+        if (permissionOwner != null) {
+            final ClipData clip = job.getClipData();
+            if (clip != null) {
+                final int N = clip.getItemCount();
+                for (int i = 0; i < N; i++) {
+                    revokeItemLocked(am, clip.getItemAt(i));
+                }
+            }
+        }
+    }
+
+    public boolean isPrepared() {
+        return prepared;
+    }
+
+    private final void grantUriLocked(IActivityManager am, Uri uri, int sourceUid,
+            String targetPackage, int targetUserId) {
+        try {
+            int sourceUserId = ContentProvider.getUserIdFromUri(uri,
+                    UserHandle.getUserId(sourceUid));
+            uri = ContentProvider.getUriWithoutUserId(uri);
+            if (permissionOwner == null) {
+                permissionOwner = am.newUriPermissionOwner("job: " + toShortString());
+            }
+            am.grantUriPermissionFromOwner(permissionOwner, sourceUid, targetPackage,
+                    uri, job.getClipGrantFlags(), sourceUserId, targetUserId);
+        } catch (RemoteException e) {
+            Slog.e("JobScheduler", "AM dead");
+        }
+    }
+
+    private final void grantItemLocked(IActivityManager am, ClipData.Item item, int sourceUid,
+            String targetPackage, int targetUserId) {
+        if (item.getUri() != null) {
+            grantUriLocked(am, item.getUri(), sourceUid, targetPackage, targetUserId);
+        }
+        Intent intent = item.getIntent();
+        if (intent != null && intent.getData() != null) {
+            grantUriLocked(am, intent.getData(), sourceUid, targetPackage, targetUserId);
+        }
+    }
+
+    private final void revokeUriLocked(IActivityManager am, Uri uri) {
+        int userId = ContentProvider.getUserIdFromUri(uri,
+                UserHandle.getUserId(Binder.getCallingUid()));
+        long ident = Binder.clearCallingIdentity();
+        try {
+            uri = ContentProvider.getUriWithoutUserId(uri);
+            am.revokeUriPermissionFromOwner(permissionOwner, uri,
+                    job.getClipGrantFlags(), userId);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private final void revokeItemLocked(IActivityManager am, ClipData.Item item) {
+        if (item.getUri() != null) {
+            revokeUriLocked(am, item.getUri());
+        }
+        Intent intent = item.getIntent();
+        if (intent != null && intent.getData() != null) {
+            revokeUriLocked(am, intent.getData());
+        }
+    }
+
     public JobInfo getJob() {
         return job;
     }
@@ -295,14 +393,6 @@
         return tag;
     }
 
-    public PersistableBundle getExtras() {
-        return job.getExtras();
-    }
-
-    public Bundle getTransientExtras() {
-        return job.getTransientExtras();
-    }
-
     public int getPriority() {
         return job.getPriority();
     }
@@ -505,22 +595,64 @@
 
     @Override
     public String toString() {
-        return String.valueOf(hashCode()).substring(0, 3) + ".."
-                + ":[" + job.getService()
-                + ",jId=" + job.getId()
-                + ",u" + getUserId()
-                + ",suid=" + getSourceUid()
-                + ",R=(" + formatRunTime(earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME)
-                + "," + formatRunTime(latestRunTimeElapsedMillis, NO_LATEST_RUNTIME) + ")"
-                + ",N=" + job.getNetworkType() + ",C=" + job.isRequireCharging()
-                + ",BL=" + job.isRequireBatteryNotLow()
-                + ",I=" + job.isRequireDeviceIdle()
-                + ",U=" + (job.getTriggerContentUris() != null)
-                + ",F=" + numFailures + ",P=" + job.isPersisted()
-                + ",ANI=" + ((satisfiedConstraints&CONSTRAINT_APP_NOT_IDLE) != 0)
-                + ",DND=" + ((satisfiedConstraints&CONSTRAINT_DEVICE_NOT_DOZING) != 0)
-                + (isReady() ? "(READY)" : "")
-                + "]";
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("JobStatus{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append(" #");
+        UserHandle.formatUid(sb, callingUid);
+        sb.append("/");
+        sb.append(job.getId());
+        sb.append(' ');
+        sb.append(batteryName);
+        sb.append(" u=");
+        sb.append(getUserId());
+        sb.append(" s=");
+        sb.append(getSourceUid());
+        if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME
+                || latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) {
+            sb.append(" TIME=");
+            sb.append(formatRunTime(earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME));
+            sb.append("-");
+            sb.append(formatRunTime(latestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME));
+        }
+        if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) {
+            sb.append(" NET=");
+            sb.append(job.getNetworkType());
+        }
+        if (job.isRequireCharging()) {
+            sb.append(" CHARGING");
+        }
+        if (job.isRequireBatteryNotLow()) {
+            sb.append(" BATNOTLOW");
+        }
+        if (job.isRequireStorageNotLow()) {
+            sb.append(" STORENOTLOW");
+        }
+        if (job.isRequireDeviceIdle()) {
+            sb.append(" IDLE");
+        }
+        if (job.isPersisted()) {
+            sb.append(" PERSISTED");
+        }
+        if ((satisfiedConstraints&CONSTRAINT_APP_NOT_IDLE) == 0) {
+            sb.append(" WAIT:APP_NOT_IDLE");
+        }
+        if ((satisfiedConstraints&CONSTRAINT_DEVICE_NOT_DOZING) == 0) {
+            sb.append(" WAIT:DEV_NOT_DOZING");
+        }
+        if (job.getTriggerContentUris() != null) {
+            sb.append(" URIS=");
+            sb.append(Arrays.toString(job.getTriggerContentUris()));
+        }
+        if (numFailures != 0) {
+            sb.append(" failures=");
+            sb.append(numFailures);
+        }
+        if (isReady()) {
+            sb.append(" READY");
+        }
+        sb.append("}");
+        return sb.toString();
     }
 
     private String formatRunTime(long runtime, long  defaultValue) {
@@ -613,8 +745,9 @@
         pw.print(" user="); pw.print(getSourceUserId());
         pw.print(" pkg="); pw.println(getSourcePackageName());
         if (full) {
-            pw.print(prefix); pw.println("JobInfo:"); pw.print(prefix);
-            pw.print("  Service: "); pw.println(job.getService().flattenToShortString());
+            pw.print(prefix); pw.println("JobInfo:");
+            pw.print(prefix); pw.print("  Service: ");
+            pw.println(job.getService().flattenToShortString());
             if (job.isPeriodic()) {
                 pw.print(prefix); pw.print("  PERIODIC: interval=");
                 TimeUtils.formatDuration(job.getIntervalMillis(), pw);
@@ -654,6 +787,20 @@
                     pw.println();
                 }
             }
+            if (job.getExtras() != null && !job.getExtras().maybeIsEmpty()) {
+                pw.print(prefix); pw.print("  Extras: ");
+                pw.println(job.getExtras().toShortString());
+            }
+            if (job.getTransientExtras() != null && !job.getTransientExtras().maybeIsEmpty()) {
+                pw.print(prefix); pw.print("  Transient extras: ");
+                pw.println(job.getTransientExtras().toShortString());
+            }
+            if (job.getClipData() != null) {
+                pw.print(prefix); pw.print("  Clip data: ");
+                StringBuilder b = new StringBuilder(128);
+                job.getClipData().toShortString(b);
+                pw.println(b);
+            }
             if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) {
                 pw.print(prefix); pw.print("  Network type: "); pw.println(job.getNetworkType());
             }
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index dca74ff..a8eb986 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -653,6 +653,11 @@
     }
 
     @Override
+    public void revokeUriPermission(String targetPackage, Uri uri, int modeFlags) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
         throw new UnsupportedOperationException();
     }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 06272c8..cc5a0c3 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -1659,6 +1659,12 @@
     }
 
     @Override
+    public void revokeUriPermission(String arg0, Uri arg1, int arg2) {
+        // pass
+
+    }
+
+    @Override
     public void sendBroadcast(Intent arg0) {
         // pass