Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1a30bd9b authored by Dianne Hackborn's avatar Dianne Hackborn
Browse files

Add and implement JobScheduler content observer APIs.

Allows one to schedule jobs to run when content URIs
change, and find out what changed when the job executes.

This required adding a new API to StateController to
tell it when we are about to start executing a job, so
we can transfer the currently collected changes out of
its internal state to fill it in to the JobParameters.

Also some additional dumpsys debug output to help
understand what is going on in the job scheduler.

Change-Id: I91f51b226ff4add7a271a8333beffa5e86c7bf18
parent aae84105
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -6186,6 +6186,7 @@ package android.app.job {
    method public long getMinLatencyMillis();
    method public int getNetworkType();
    method public android.content.ComponentName getService();
    method public android.app.job.JobInfo.TriggerContentUri[] getTriggerContentUris();
    method public boolean isPeriodic();
    method public boolean isPersisted();
    method public boolean isRequireCharging();
@@ -6205,6 +6206,7 @@ package android.app.job {
  public static final class JobInfo.Builder {
    ctor public JobInfo.Builder(int, android.content.ComponentName);
    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 setExtras(android.os.PersistableBundle);
@@ -6218,10 +6220,22 @@ package android.app.job {
    method public android.app.job.JobInfo.Builder setRequiresDeviceIdle(boolean);
  }
  public static final class JobInfo.TriggerContentUri implements android.os.Parcelable {
    ctor public JobInfo.TriggerContentUri(android.net.Uri, int);
    method public int describeContents();
    method public int getFlags();
    method public android.net.Uri getUri();
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.app.job.JobInfo.TriggerContentUri> CREATOR;
    field public static final int FLAG_NOTIFY_FOR_DESCENDANTS = 1; // 0x1
  }
  public class JobParameters implements android.os.Parcelable {
    method public int describeContents();
    method public android.os.PersistableBundle getExtras();
    method public int getJobId();
    method public java.lang.String[] getTriggeredContentAuthorities();
    method public android.net.Uri[] getTriggeredContentUris();
    method public boolean isOverrideDeadlineExpired();
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.app.job.JobParameters> CREATOR;
+14 −0
Original line number Diff line number Diff line
@@ -6440,6 +6440,7 @@ package android.app.job {
    method public long getMinLatencyMillis();
    method public int getNetworkType();
    method public android.content.ComponentName getService();
    method public android.app.job.JobInfo.TriggerContentUri[] getTriggerContentUris();
    method public boolean isPeriodic();
    method public boolean isPersisted();
    method public boolean isRequireCharging();
@@ -6459,6 +6460,7 @@ package android.app.job {
  public static final class JobInfo.Builder {
    ctor public JobInfo.Builder(int, android.content.ComponentName);
    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 setExtras(android.os.PersistableBundle);
@@ -6472,10 +6474,22 @@ package android.app.job {
    method public android.app.job.JobInfo.Builder setRequiresDeviceIdle(boolean);
  }
  public static final class JobInfo.TriggerContentUri implements android.os.Parcelable {
    ctor public JobInfo.TriggerContentUri(android.net.Uri, int);
    method public int describeContents();
    method public int getFlags();
    method public android.net.Uri getUri();
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.app.job.JobInfo.TriggerContentUri> CREATOR;
    field public static final int FLAG_NOTIFY_FOR_DESCENDANTS = 1; // 0x1
  }
  public class JobParameters implements android.os.Parcelable {
    method public int describeContents();
    method public android.os.PersistableBundle getExtras();
    method public int getJobId();
    method public java.lang.String[] getTriggeredContentAuthorities();
    method public android.net.Uri[] getTriggeredContentUris();
    method public boolean isOverrideDeadlineExpired();
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.app.job.JobParameters> CREATOR;
+14 −0
Original line number Diff line number Diff line
@@ -6188,6 +6188,7 @@ package android.app.job {
    method public long getMinLatencyMillis();
    method public int getNetworkType();
    method public android.content.ComponentName getService();
    method public android.app.job.JobInfo.TriggerContentUri[] getTriggerContentUris();
    method public boolean isPeriodic();
    method public boolean isPersisted();
    method public boolean isRequireCharging();
@@ -6207,6 +6208,7 @@ package android.app.job {
  public static final class JobInfo.Builder {
    ctor public JobInfo.Builder(int, android.content.ComponentName);
    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 setExtras(android.os.PersistableBundle);
@@ -6220,10 +6222,22 @@ package android.app.job {
    method public android.app.job.JobInfo.Builder setRequiresDeviceIdle(boolean);
  }
  public static final class JobInfo.TriggerContentUri implements android.os.Parcelable {
    ctor public JobInfo.TriggerContentUri(android.net.Uri, int);
    method public int describeContents();
    method public int getFlags();
    method public android.net.Uri getUri();
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.app.job.JobInfo.TriggerContentUri> CREATOR;
    field public static final int FLAG_NOTIFY_FOR_DESCENDANTS = 1; // 0x1
  }
  public class JobParameters implements android.os.Parcelable {
    method public int describeContents();
    method public android.os.PersistableBundle getExtras();
    method public int getJobId();
    method public java.lang.String[] getTriggeredContentAuthorities();
    method public android.net.Uri[] getTriggeredContentUris();
    method public boolean isOverrideDeadlineExpired();
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.app.job.JobParameters> CREATOR;
+119 −1
Original line number Diff line number Diff line
@@ -16,11 +16,16 @@

package android.app.job;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;

import java.util.ArrayList;

/**
 * Container of data passed to the {@link android.app.job.JobScheduler} fully encapsulating the
 * parameters required to schedule work against the calling application. These are constructed
@@ -80,6 +85,7 @@ public class JobInfo implements Parcelable {
    private final ComponentName service;
    private final boolean requireCharging;
    private final boolean requireDeviceIdle;
    private final TriggerContentUri[] triggerContentUris;
    private final boolean hasEarlyConstraint;
    private final boolean hasLateConstraint;
    private final int networkType;
@@ -133,6 +139,15 @@ public class JobInfo implements Parcelable {
        return requireDeviceIdle;
    }

    /**
     * Which content: URIs must change for the job to be scheduled.  Returns null
     * if there are none required.
     */
    @Nullable
    public TriggerContentUri[] getTriggerContentUris() {
        return triggerContentUris;
    }

    /**
     * One of {@link android.app.job.JobInfo#NETWORK_TYPE_ANY},
     * {@link android.app.job.JobInfo#NETWORK_TYPE_NONE}, or
@@ -232,6 +247,7 @@ public class JobInfo implements Parcelable {
        service = in.readParcelable(null);
        requireCharging = in.readInt() == 1;
        requireDeviceIdle = in.readInt() == 1;
        triggerContentUris = in.createTypedArray(TriggerContentUri.CREATOR);
        networkType = in.readInt();
        minLatencyMillis = in.readLong();
        maxExecutionDelayMillis = in.readLong();
@@ -252,6 +268,9 @@ public class JobInfo implements Parcelable {
        service = b.mJobService;
        requireCharging = b.mRequiresCharging;
        requireDeviceIdle = b.mRequiresDeviceIdle;
        triggerContentUris = b.mTriggerContentUris != null
                ? b.mTriggerContentUris.toArray(new TriggerContentUri[b.mTriggerContentUris.size()])
                : null;
        networkType = b.mNetworkType;
        minLatencyMillis = b.mMinLatencyMillis;
        maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
@@ -278,6 +297,7 @@ public class JobInfo implements Parcelable {
        out.writeParcelable(service, flags);
        out.writeInt(requireCharging ? 1 : 0);
        out.writeInt(requireDeviceIdle ? 1 : 0);
        out.writeTypedArray(triggerContentUris, flags);
        out.writeInt(networkType);
        out.writeLong(minLatencyMillis);
        out.writeLong(maxExecutionDelayMillis);
@@ -309,6 +329,75 @@ public class JobInfo implements Parcelable {
        return "(job:" + jobId + "/" + service.flattenToShortString() + ")";
    }

    /**
     * Information about a content URI modification that a job would like to
     * trigger on.
     */
    public static final class TriggerContentUri implements Parcelable {
        private final Uri mUri;
        private final int mFlags;

        /**
         * Flag for trigger: also trigger if any descendants of the given URI change.
         * Corresponds to the <var>notifyForDescendants</var> of
         * {@link android.content.ContentResolver#registerContentObserver}.
         */
        public static final int FLAG_NOTIFY_FOR_DESCENDANTS = 1<<0;

        /**
         * Create a new trigger description.
         * @param uri The URI to observe.  Must be non-null.
         * @param flags Optional flags for the observer, either 0 or
         * {@link #FLAG_NOTIFY_FOR_DESCENDANTS}.
         */
        public TriggerContentUri(@NonNull Uri uri, int flags) {
            mUri = uri;
            mFlags = flags;
        }

        /**
         * Return the Uri this trigger was created for.
         */
        public Uri getUri() {
            return mUri;
        }

        /**
         * Return the flags supplied for the trigger.
         */
        public int getFlags() {
            return mFlags;
        }

        private TriggerContentUri(Parcel in) {
            mUri = Uri.CREATOR.createFromParcel(in);
            mFlags = in.readInt();
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            mUri.writeToParcel(out, flags);
            out.writeInt(mFlags);
        }

        public static final Creator<TriggerContentUri> CREATOR = new Creator<TriggerContentUri>() {
            @Override
            public TriggerContentUri createFromParcel(Parcel in) {
                return new TriggerContentUri(in);
            }

            @Override
            public TriggerContentUri[] newArray(int size) {
                return new TriggerContentUri[size];
            }
        };
    }

    /** Builder class for constructing {@link JobInfo} objects. */
    public static final class Builder {
        private int mJobId;
@@ -319,6 +408,7 @@ public class JobInfo implements Parcelable {
        private boolean mRequiresCharging;
        private boolean mRequiresDeviceIdle;
        private int mNetworkType;
        private ArrayList<TriggerContentUri> mTriggerContentUris;
        private boolean mIsPersisted;
        // One-off parameters.
        private long mMinLatencyMillis;
@@ -402,6 +492,25 @@ public class JobInfo implements Parcelable {
            return this;
        }

        /**
         * Add a new content: URI that will be monitored with a
         * {@link android.database.ContentObserver}, and will cause the job to execute if changed.
         * If you have any trigger content URIs associated with a job, it will not execute until
         * there has been a change report for one or more of them.
         * <p>Note that trigger URIs can not be used in combination with
         * {@link #setPeriodic(long)} or {@link #setPersisted(boolean)}.  To continually monitor
         * for content changes, you need to schedule a new JobInfo observing the same URIs
         * before you finish execution of the JobService handling the most recent changes.</p>
         * @param uri The content: URI to monitor.
         */
        public Builder addTriggerContentUri(@NonNull TriggerContentUri uri) {
            if (mTriggerContentUris == null) {
                mTriggerContentUris = new ArrayList<>();
            }
            mTriggerContentUris.add(uri);
            return this;
        }

        /**
         * Specify that this job should recur with the provided interval, not more than once per
         * period. You have no control over when within this interval this job will be executed,
@@ -498,7 +607,8 @@ public class JobInfo implements Parcelable {
        public JobInfo build() {
            // Allow jobs with no constraints - What am I, a database?
            if (!mHasEarlyConstraint && !mHasLateConstraint && !mRequiresCharging &&
                    !mRequiresDeviceIdle && mNetworkType == NETWORK_TYPE_NONE) {
                    !mRequiresDeviceIdle && mNetworkType == NETWORK_TYPE_NONE &&
                    mTriggerContentUris == null) {
                throw new IllegalArgumentException("You're trying to build a job with no " +
                        "constraints, this is not allowed.");
            }
@@ -512,6 +622,14 @@ public class JobInfo implements Parcelable {
                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 (mBackoffPolicySet && mRequiresDeviceIdle) {
                throw new IllegalArgumentException("An idle mode job will not respect any" +
                        " back-off policy, so calling setBackoffCriteria with" +
+36 −1
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.app.job;

import android.app.job.IJobCallback;
import android.net.Uri;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -43,15 +44,21 @@ public class JobParameters implements Parcelable {
    private final PersistableBundle extras;
    private final IBinder callback;
    private final boolean overrideDeadlineExpired;
    private final Uri[] mTriggeredContentUris;
    private final String[] mTriggeredContentAuthorities;

    private int stopReason; // Default value of stopReason is REASON_CANCELED

    /** @hide */
    public JobParameters(IBinder callback, int jobId, PersistableBundle extras,
                         boolean overrideDeadlineExpired) {
                boolean overrideDeadlineExpired, Uri[] triggeredContentUris,
            String[] triggeredContentAuthorities) {
        this.jobId = jobId;
        this.extras = extras;
        this.callback = callback;
        this.overrideDeadlineExpired = overrideDeadlineExpired;
        this.mTriggeredContentUris = triggeredContentUris;
        this.mTriggeredContentAuthorities = triggeredContentAuthorities;
    }

    /**
@@ -88,6 +95,30 @@ public class JobParameters implements Parcelable {
        return overrideDeadlineExpired;
    }

    /**
     * For jobs with {@link android.app.job.JobInfo.Builder#addTriggerContentUri} set, this
     * reports which URIs have triggered the job.  This will be null if either no URIs have
     * triggered it (it went off due to a deadline or other reason), or the number of changed
     * URIs is too large to report.  Whether or not the number of URIs is too large, you can
     * always use {@link #getTriggeredContentAuthorities()} to determine whether the job was
     * triggered due to any content changes and the authorities they are associated with.
     */
    public Uri[] getTriggeredContentUris() {
        return mTriggeredContentUris;
    }

    /**
     * For jobs with {@link android.app.job.JobInfo.Builder#addTriggerContentUri} set, this
     * reports which content authorities have triggered the job.  It will only be null if no
     * authorities have triggered it -- that is, the job executed for some other reason, such
     * as a deadline expiring.  If this is non-null, you can use {@link #getTriggeredContentUris()}
     * to retrieve the details of which URIs changed (as long as that has not exceeded the maximum
     * number it can reported).
     */
    public String[] getTriggeredContentAuthorities() {
        return mTriggeredContentAuthorities;
    }

    /** @hide */
    public IJobCallback getCallback() {
        return IJobCallback.Stub.asInterface(callback);
@@ -98,6 +129,8 @@ public class JobParameters implements Parcelable {
        extras = in.readPersistableBundle();
        callback = in.readStrongBinder();
        overrideDeadlineExpired = in.readInt() == 1;
        mTriggeredContentUris = in.createTypedArray(Uri.CREATOR);
        mTriggeredContentAuthorities = in.createStringArray();
        stopReason = in.readInt();
    }

@@ -117,6 +150,8 @@ public class JobParameters implements Parcelable {
        dest.writePersistableBundle(extras);
        dest.writeStrongBinder(callback);
        dest.writeInt(overrideDeadlineExpired ? 1 : 0);
        dest.writeTypedArray(mTriggeredContentUris, flags);
        dest.writeStringArray(mTriggeredContentAuthorities);
        dest.writeInt(stopReason);
    }

Loading