diff options
9 files changed, 357 insertions, 62 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 7f02cb36dfc1..2f6b6897addb 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -432,6 +432,7 @@ public class JobInfo implements Parcelable { @UnsupportedAppUsage private final ComponentName service; private final int constraintFlags; + private final int mPreferredConstraintFlags; private final TriggerContentUri[] triggerContentUris; private final long triggerContentUpdateDelay; private final long triggerContentMaxDelay; @@ -522,6 +523,30 @@ public class JobInfo implements Parcelable { } /** + * @hide + * @see JobInfo.Builder#setPrefersBatteryNotLow(boolean) + */ + public boolean isPreferBatteryNotLow() { + return (mPreferredConstraintFlags & CONSTRAINT_FLAG_BATTERY_NOT_LOW) != 0; + } + + /** + * @hide + * @see JobInfo.Builder#setPrefersCharging(boolean) + */ + public boolean isPreferCharging() { + return (mPreferredConstraintFlags & CONSTRAINT_FLAG_CHARGING) != 0; + } + + /** + * @hide + * @see JobInfo.Builder#setPrefersDeviceIdle(boolean) + */ + public boolean isPreferDeviceIdle() { + return (mPreferredConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0; + } + + /** * @see JobInfo.Builder#setRequiresCharging(boolean) */ public boolean isRequireCharging() { @@ -557,6 +582,13 @@ public class JobInfo implements Parcelable { } /** + * @hide + */ + public int getPreferredConstraintFlags() { + return mPreferredConstraintFlags; + } + + /** * Which content: URIs must change for the job to be scheduled. Returns null * if there are none required. * @see JobInfo.Builder#addTriggerContentUri(TriggerContentUri) @@ -800,6 +832,9 @@ public class JobInfo implements Parcelable { if (constraintFlags != j.constraintFlags) { return false; } + if (mPreferredConstraintFlags != j.mPreferredConstraintFlags) { + return false; + } if (!Arrays.equals(triggerContentUris, j.triggerContentUris)) { return false; } @@ -880,6 +915,7 @@ public class JobInfo implements Parcelable { hashCode = 31 * hashCode + service.hashCode(); } hashCode = 31 * hashCode + constraintFlags; + hashCode = 31 * hashCode + mPreferredConstraintFlags; if (triggerContentUris != null) { hashCode = 31 * hashCode + Arrays.hashCode(triggerContentUris); } @@ -922,6 +958,7 @@ public class JobInfo implements Parcelable { } service = in.readParcelable(null); constraintFlags = in.readInt(); + mPreferredConstraintFlags = in.readInt(); triggerContentUris = in.createTypedArray(TriggerContentUri.CREATOR); triggerContentUpdateDelay = in.readLong(); triggerContentMaxDelay = in.readLong(); @@ -956,6 +993,7 @@ public class JobInfo implements Parcelable { clipGrantFlags = b.mClipGrantFlags; service = b.mJobService; constraintFlags = b.mConstraintFlags; + mPreferredConstraintFlags = b.mPreferredConstraintFlags; triggerContentUris = b.mTriggerContentUris != null ? b.mTriggerContentUris.toArray(new TriggerContentUri[b.mTriggerContentUris.size()]) : null; @@ -999,6 +1037,7 @@ public class JobInfo implements Parcelable { } out.writeParcelable(service, flags); out.writeInt(constraintFlags); + out.writeInt(mPreferredConstraintFlags); out.writeTypedArray(triggerContentUris, flags); out.writeLong(triggerContentUpdateDelay); out.writeLong(triggerContentMaxDelay); @@ -1146,6 +1185,7 @@ public class JobInfo implements Parcelable { private int mFlags; // Requirements. private int mConstraintFlags; + private int mPreferredConstraintFlags; private NetworkRequest mNetworkRequest; private long mNetworkDownloadBytes = NETWORK_BYTES_UNKNOWN; private long mNetworkUploadBytes = NETWORK_BYTES_UNKNOWN; @@ -1199,6 +1239,7 @@ public class JobInfo implements Parcelable { mBias = job.getBias(); mFlags = job.getFlags(); mConstraintFlags = job.getConstraintFlags(); + mPreferredConstraintFlags = job.getPreferredConstraintFlags(); mNetworkRequest = job.getRequiredNetwork(); mNetworkDownloadBytes = job.getEstimatedNetworkDownloadBytes(); mNetworkUploadBytes = job.getEstimatedNetworkUploadBytes(); @@ -1341,9 +1382,6 @@ public class JobInfo implements Parcelable { * Calling this method will override any requirements previously defined * by {@link #setRequiredNetwork(NetworkRequest)}; you typically only * want to call one of these methods. - * <p> Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, - * {@link JobScheduler} may try to shift the execution of jobs requiring - * {@link #NETWORK_TYPE_ANY} to when there is access to an un-metered network. * <p class="note"> * When your job executes in * {@link JobService#onStartJob(JobParameters)}, be sure to use the @@ -1505,10 +1543,105 @@ public class JobInfo implements Parcelable { } /** - * Specify that to run this job, the device must be charging (or be a + * Specify that this job would prefer to be run when the device's battery is not low. + * This defaults to {@code false}. + * + * <p>The system may attempt to delay this job until the device's battery is not low, + * but may choose to run it even if the device's battery is low. JobScheduler will not stop + * this job if this constraint is no longer satisfied after the job has started running. + * If this job must only run when the device's battery is not low, + * use {@link #setRequiresBatteryNotLow(boolean)} instead. + * + * <p> + * Because it doesn't make sense for a constraint to be both preferred and required, + * calling both this and {@link #setRequiresBatteryNotLow(boolean)} with {@code true} + * will result in an {@link java.lang.IllegalArgumentException} when + * {@link android.app.job.JobInfo.Builder#build()} is called. + * + * @param prefersBatteryNotLow Pass {@code true} to prefer that the device's battery level + * not be low in order to run the job. + * @return This object for method chaining + * @see JobInfo#isPreferBatteryNotLow() + * @hide + */ + @NonNull + public Builder setPrefersBatteryNotLow(boolean prefersBatteryNotLow) { + mPreferredConstraintFlags = + (mPreferredConstraintFlags & ~CONSTRAINT_FLAG_BATTERY_NOT_LOW) + | (prefersBatteryNotLow ? CONSTRAINT_FLAG_BATTERY_NOT_LOW : 0); + return this; + } + + /** + * Specify that this job would prefer to be run when the device is charging (or be a * non-battery-powered device connected to permanent power, such as Android TV * devices). This defaults to {@code false}. * + * <p> + * The system may attempt to delay this job until the device is charging, but may + * choose to run it even if the device is not charging. JobScheduler will not stop + * this job if this constraint is no longer satisfied after the job has started running. + * If this job must only run when the device is charging, + * use {@link #setRequiresCharging(boolean)} instead. + * + * <p> + * Because it doesn't make sense for a constraint to be both preferred and required, + * calling both this and {@link #setRequiresCharging(boolean)} with {@code true} + * will result in an {@link java.lang.IllegalArgumentException} when + * {@link android.app.job.JobInfo.Builder#build()} is called. + * + * @param prefersCharging Pass {@code true} to prefer that the device be + * charging in order to run the job. + * @return This object for method chaining + * @see JobInfo#isPreferCharging() + * @hide + */ + @NonNull + public Builder setPrefersCharging(boolean prefersCharging) { + mPreferredConstraintFlags = (mPreferredConstraintFlags & ~CONSTRAINT_FLAG_CHARGING) + | (prefersCharging ? CONSTRAINT_FLAG_CHARGING : 0); + return this; + } + + /** + * Specify that this job would prefer to be run when the device is not in active use. + * This defaults to {@code false}. + * + * <p>The system may attempt to delay this job until the device is not in active use, + * but may choose to run it even if the device is not idle. JobScheduler will not stop + * this job if this constraint is no longer satisfied after the job has started running. + * If this job must only run when the device is not in active use, + * use {@link #setRequiresDeviceIdle(boolean)} instead. + * + * <p> + * Because it doesn't make sense for a constraint to be both preferred and required, + * calling both this and {@link #setRequiresDeviceIdle(boolean)} with {@code true} + * will result in an {@link java.lang.IllegalArgumentException} when + * {@link android.app.job.JobInfo.Builder#build()} is called. + * + * <p class="note">Despite the similar naming, this job constraint is <em>not</em> + * related to the system's "device idle" or "doze" states. This constraint only + * determines whether a job is allowed to run while the device is directly in use. + * + * @param prefersDeviceIdle Pass {@code true} to prefer that the device not be in active + * use when running this job. + * @return This object for method chaining + * @see JobInfo#isRequireDeviceIdle() + * @hide + */ + @NonNull + public Builder setPrefersDeviceIdle(boolean prefersDeviceIdle) { + mPreferredConstraintFlags = (mPreferredConstraintFlags & ~CONSTRAINT_FLAG_DEVICE_IDLE) + | (prefersDeviceIdle ? CONSTRAINT_FLAG_DEVICE_IDLE : 0); + return this; + } + + /** + * Specify that to run this job, the device must be charging (or be a + * non-battery-powered device connected to permanent power, such as Android TV + * devices). This defaults to {@code false}. Setting this to {@code false} <b>DOES NOT</b> + * mean the job will only run when the device is not charging. + * * <p class="note">For purposes of running jobs, a battery-powered device * "charging" is not quite the same as simply being connected to power. If the * device is so busy that the battery is draining despite a power connection, jobs @@ -1530,7 +1663,9 @@ public class JobInfo implements Parcelable { * Specify that to run this job, the device's battery level must not be low. * This defaults to false. If true, the job will only run when the battery level * is not low, which is generally the point where the user is given a "low battery" - * warning. + * warning. Setting this to {@code false} <b>DOES NOT</b> mean the job will only run + * when the battery is low. + * * @param batteryNotLow Whether or not the device's battery level must not be low. * @see JobInfo#isRequireBatteryNotLow() */ @@ -1543,7 +1678,8 @@ public class JobInfo implements Parcelable { /** * When set {@code true}, ensure that this job will not run if the device is in active use. * The default state is {@code false}: that is, the for the job to be runnable even when - * someone is interacting with the device. + * someone is interacting with the device. Setting this to {@code false} <b>DOES NOT</b> + * mean the job will only run when the device is not idle. * * <p>This state is a loose definition provided by the system. In general, it means that * the device is not currently being used interactively, and has not been in use for some @@ -2156,6 +2292,29 @@ public class JobInfo implements Parcelable { } } + if ((constraintFlags & mPreferredConstraintFlags) != 0) { + // Something is marked as both preferred and required. Try to give a clear exception + // reason. + if ((constraintFlags & CONSTRAINT_FLAG_BATTERY_NOT_LOW) != 0 + && (mPreferredConstraintFlags & CONSTRAINT_FLAG_BATTERY_NOT_LOW) != 0) { + throw new IllegalArgumentException( + "battery-not-low constraint cannot be both preferred and required"); + } + if ((constraintFlags & CONSTRAINT_FLAG_CHARGING) != 0 + && (mPreferredConstraintFlags & CONSTRAINT_FLAG_CHARGING) != 0) { + throw new IllegalArgumentException( + "charging constraint cannot be both preferred and required"); + } + if ((constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0 + && (mPreferredConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) { + throw new IllegalArgumentException( + "device idle constraint cannot be both preferred and required"); + } + // Couldn't figure out what the overlap was. Just use a generic message. + throw new IllegalArgumentException( + "constraints cannot be both preferred and required"); + } + if (isUserInitiated) { if (hasEarlyConstraint) { throw new IllegalArgumentException("A user-initiated job cannot have a time delay"); @@ -2173,7 +2332,8 @@ public class JobInfo implements Parcelable { if (mPriority != PRIORITY_MAX) { throw new IllegalArgumentException("A user-initiated job must be max priority."); } - if ((constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) { + if ((constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0 + || (mPreferredConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) { throw new IllegalArgumentException( "A user-initiated job cannot have a device-idle constraint"); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java index 0dcb0b2456d6..a96a4ef951ea 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java @@ -883,6 +883,15 @@ public final class JobStore { if (job.isRequireStorageNotLow()) { out.attribute(null, "storage-not-low", Boolean.toString(true)); } + if (job.isPreferBatteryNotLow()) { + out.attributeBoolean(null, "prefer-battery-not-low", true); + } + if (job.isPreferCharging()) { + out.attributeBoolean(null, "prefer-charging", true); + } + if (job.isPreferDeviceIdle()) { + out.attributeBoolean(null, "prefer-idle", true); + } out.endTag(null, XML_TAG_PARAMS_CONSTRAINTS); } @@ -1538,6 +1547,13 @@ public final class JobStore { if (val != null) { jobBuilder.setRequiresStorageNotLow(true); } + + jobBuilder.setPrefersBatteryNotLow( + parser.getAttributeBoolean(null, "prefer-battery-not-low", false)); + jobBuilder.setPrefersCharging( + parser.getAttributeBoolean(null, "prefer-charging", false)); + jobBuilder.setPrefersDeviceIdle( + parser.getAttributeBoolean(null, "prefer-idle", false)); } /** diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index e55bda7fab02..3859d89c22cd 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java @@ -1147,10 +1147,9 @@ public final class ConnectivityController extends RestrictingController implemen final boolean changed = jobStatus.setConnectivityConstraintSatisfied(nowElapsed, satisfied); + jobStatus.setHasAccessToUnmetered(satisfied && capabilities != null + && capabilities.hasCapability(NET_CAPABILITY_NOT_METERED)); if (jobStatus.getPreferUnmetered()) { - jobStatus.setHasAccessToUnmetered(satisfied && capabilities != null - && capabilities.hasCapability(NET_CAPABILITY_NOT_METERED)); - jobStatus.setFlexibilityConstraintSatisfied(nowElapsed, mFlexibilityController.isFlexibilitySatisfiedLocked(jobStatus)); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java index 620c48d2c343..234a93c8d168 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java @@ -239,14 +239,14 @@ public final class FlexibilityController extends StateController { return !mFlexibilityEnabled || mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP || mService.isCurrentlyRunningLocked(js) - || getNumSatisfiedRequiredConstraintsLocked(js) + || getNumSatisfiedFlexibleConstraintsLocked(js) >= js.getNumRequiredFlexibleConstraints(); } @VisibleForTesting @GuardedBy("mLock") - int getNumSatisfiedRequiredConstraintsLocked(JobStatus js) { - return Integer.bitCount(mSatisfiedFlexibleConstraints) + int getNumSatisfiedFlexibleConstraintsLocked(JobStatus js) { + return Integer.bitCount(mSatisfiedFlexibleConstraints & js.getPreferredConstraintFlags()) + (js.getHasAccessToUnmetered() ? 1 : 0); } @@ -651,7 +651,7 @@ public final class FlexibilityController extends StateController { static final String KEY_RESCHEDULED_JOB_DEADLINE_MS = FC_CONFIG_PREFIX + "rescheduled_job_deadline_ms"; - private static final boolean DEFAULT_FLEXIBILITY_ENABLED = false; + private static final boolean DEFAULT_FLEXIBILITY_ENABLED = true; @VisibleForTesting static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS; @VisibleForTesting diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 0cc775870f88..17dde9098926 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -16,7 +16,6 @@ package com.android.server.job.controllers; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; @@ -25,8 +24,6 @@ import static com.android.server.job.JobSchedulerService.NEVER_INDEX; import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; import static com.android.server.job.JobSchedulerService.WORKING_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; -import static com.android.server.job.controllers.FlexibilityController.NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS; -import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; @@ -115,12 +112,11 @@ public final class JobStatus { static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24; // Implicit constraint static final int CONSTRAINT_PREFETCH = 1 << 23; static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint - static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint + static final int CONSTRAINT_FLEXIBLE = 1 << 21; private static final int IMPLICIT_CONSTRAINTS = 0 | CONSTRAINT_BACKGROUND_NOT_RESTRICTED | CONSTRAINT_DEVICE_NOT_DOZING - | CONSTRAINT_FLEXIBLE | CONSTRAINT_TARE_WEALTH | CONSTRAINT_WITHIN_QUOTA; @@ -298,6 +294,7 @@ public final class JobStatus { // Constraints. final int requiredConstraints; + private final int mPreferredConstraints; private final int mRequiredConstraintsOfInterest; int satisfiedConstraints = 0; private int mSatisfiedConstraintsOfInterest = 0; @@ -618,24 +615,26 @@ public final class JobStatus { } mHasExemptedMediaUrisOnly = exemptedMediaUrisOnly; - mPreferUnmetered = job.getRequiredNetwork() != null - && !job.getRequiredNetwork().hasCapability(NET_CAPABILITY_NOT_METERED); + mPreferredConstraints = job.getPreferredConstraintFlags(); + + // Exposing a preferredNetworkRequest API requires that we make sure that the preferred + // NetworkRequest is a subset of the required NetworkRequest. We currently don't have the + // code to ensure that, so disable this part for now. + // TODO(236261941): look into enabling flexible network constraint requests + mPreferUnmetered = false; + // && job.getRequiredNetwork() != null + // && !job.getRequiredNetwork().hasCapability(NET_CAPABILITY_NOT_METERED); - final boolean lacksSomeFlexibleConstraints = - ((~requiredConstraints) & SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS) != 0 - || mPreferUnmetered; final boolean satisfiesMinWindowException = (latestRunTimeElapsedMillis - earliestRunTimeElapsedMillis) >= MIN_WINDOW_FOR_FLEXIBILITY_MS; // The first time a job is rescheduled it will not be subject to flexible constraints. // Otherwise, every consecutive reschedule increases a jobs' flexibility deadline. - if (!isRequestedExpeditedJob() && !job.isUserInitiated() + if (mPreferredConstraints != 0 && !isRequestedExpeditedJob() && !job.isUserInitiated() && satisfiesMinWindowException - && (numFailures + numSystemStops) != 1 - && lacksSomeFlexibleConstraints) { - mNumRequiredFlexibleConstraints = - NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0); + && (numFailures + numSystemStops) != 1) { + mNumRequiredFlexibleConstraints = Integer.bitCount(mPreferredConstraints); requiredConstraints |= CONSTRAINT_FLEXIBLE; } else { mNumRequiredFlexibleConstraints = 0; @@ -1142,6 +1141,10 @@ public final class JobStatus { mInternalFlags |= flags; } + int getPreferredConstraintFlags() { + return mPreferredConstraints; + } + public int getSatisfiedConstraintFlags() { return satisfiedConstraints; } @@ -2501,6 +2504,9 @@ public final class JobStatus { pw.print("Required constraints:"); dumpConstraints(pw, requiredConstraints); pw.println(); + pw.print("Preferred constraints:"); + dumpConstraints(pw, mPreferredConstraints); + pw.println(); pw.print("Dynamic constraints:"); dumpConstraints(pw, mDynamicConstraints); pw.println(); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java index 32e5c836ac3d..a3ae83428af5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java @@ -840,8 +840,9 @@ public class ConnectivityControllerTest { final JobStatus blue = createJobStatus(createJob() .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE); + // Unmetered preference is disabled for now. assertFalse(red.getPreferUnmetered()); - assertTrue(blue.getPreferUnmetered()); + assertFalse(blue.getPreferUnmetered()); controller.maybeStartTrackingJobLocked(red, null); controller.maybeStartTrackingJobLocked(blue, null); @@ -895,7 +896,7 @@ public class ConnectivityControllerTest { generalCallback.onLost(meteredNet); assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(red.getHasAccessToUnmetered()); + assertTrue(red.getHasAccessToUnmetered()); assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); assertTrue(blue.getHasAccessToUnmetered()); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java index 6bc552c7f32e..4b19bbb72805 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java @@ -189,7 +189,10 @@ public class FlexibilityControllerTest { } private static JobInfo.Builder createJob(int id) { - return new JobInfo.Builder(id, new ComponentName("foo", "bar")); + return new JobInfo.Builder(id, new ComponentName("foo", "bar")) + .setPrefersBatteryNotLow(true) + .setPrefersCharging(true) + .setPrefersDeviceIdle(true); } private JobStatus createJobStatus(String testTag, JobInfo.Builder job) { @@ -533,12 +536,15 @@ public class FlexibilityControllerTest { jb = createJob(i); if (i > 0) { jb.setRequiresDeviceIdle(true); + jb.setPrefersDeviceIdle(false); } if (i > 1) { jb.setRequiresBatteryNotLow(true); + jb.setPrefersBatteryNotLow(false); } if (i > 2) { jb.setRequiresCharging(true); + jb.setPrefersCharging(false); } jobs[i] = createJobStatus("", jb); flexTracker.add(jobs[i]); @@ -547,53 +553,55 @@ public class FlexibilityControllerTest { synchronized (mFlexibilityController.mLock) { ArrayList<ArraySet<JobStatus>> trackedJobs = flexTracker.getArrayList(); assertEquals(1, trackedJobs.get(0).size()); - assertEquals(0, trackedJobs.get(1).size()); - assertEquals(0, trackedJobs.get(2).size()); - assertEquals(3, trackedJobs.get(3).size()); + assertEquals(1, trackedJobs.get(1).size()); + assertEquals(1, trackedJobs.get(2).size()); + assertEquals(1, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME); assertEquals(1, trackedJobs.get(0).size()); - assertEquals(0, trackedJobs.get(1).size()); - assertEquals(1, trackedJobs.get(2).size()); - assertEquals(2, trackedJobs.get(3).size()); + assertEquals(1, trackedJobs.get(1).size()); + assertEquals(2, trackedJobs.get(2).size()); + assertEquals(0, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME); assertEquals(1, trackedJobs.get(0).size()); - assertEquals(1, trackedJobs.get(1).size()); - assertEquals(0, trackedJobs.get(2).size()); - assertEquals(2, trackedJobs.get(3).size()); + assertEquals(2, trackedJobs.get(1).size()); + assertEquals(1, trackedJobs.get(2).size()); + assertEquals(0, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME); assertEquals(2, trackedJobs.get(0).size()); - assertEquals(0, trackedJobs.get(1).size()); - assertEquals(0, trackedJobs.get(2).size()); - assertEquals(2, trackedJobs.get(3).size()); + assertEquals(1, trackedJobs.get(1).size()); + assertEquals(1, trackedJobs.get(2).size()); + assertEquals(0, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); flexTracker.remove(jobs[1]); assertEquals(2, trackedJobs.get(0).size()); - assertEquals(0, trackedJobs.get(1).size()); + assertEquals(1, trackedJobs.get(1).size()); assertEquals(0, trackedJobs.get(2).size()); - assertEquals(1, trackedJobs.get(3).size()); + assertEquals(0, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); flexTracker.resetJobNumDroppedConstraints(jobs[0], FROZEN_TIME); assertEquals(1, trackedJobs.get(0).size()); - assertEquals(0, trackedJobs.get(1).size()); + assertEquals(1, trackedJobs.get(1).size()); assertEquals(0, trackedJobs.get(2).size()); - assertEquals(2, trackedJobs.get(3).size()); + assertEquals(1, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); flexTracker.adjustJobsRequiredConstraints(jobs[0], -2, FROZEN_TIME); assertEquals(1, trackedJobs.get(0).size()); - assertEquals(1, trackedJobs.get(1).size()); + assertEquals(2, trackedJobs.get(1).size()); assertEquals(0, trackedJobs.get(2).size()); - assertEquals(1, trackedJobs.get(3).size()); + assertEquals(0, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); + // Over halfway through the flex window. The job that prefers all flex constraints + // should have its first flex constraint dropped. final long nowElapsed = ((DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 2) + HOUR_IN_MILLIS); JobSchedulerService.sElapsedRealtimeClock = @@ -601,9 +609,9 @@ public class FlexibilityControllerTest { flexTracker.resetJobNumDroppedConstraints(jobs[0], nowElapsed); assertEquals(1, trackedJobs.get(0).size()); - assertEquals(0, trackedJobs.get(1).size()); + assertEquals(1, trackedJobs.get(1).size()); assertEquals(1, trackedJobs.get(2).size()); - assertEquals(1, trackedJobs.get(3).size()); + assertEquals(0, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); } } @@ -618,8 +626,13 @@ public class FlexibilityControllerTest { @Test public void testExceptions_UserInitiated() { - JobInfo.Builder jb = createJob(0); - jb.setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); + JobInfo.Builder jb = createJob(0) + .setUserInitiated(true) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) + // Attempt to add flex constraints to the job. For now, we will ignore them. + .setPrefersBatteryNotLow(true) + .setPrefersCharging(true) + .setPrefersDeviceIdle(false); JobStatus js = createJobStatus("testExceptions_UserInitiated", jb); assertFalse(js.hasFlexibilityConstraint()); } @@ -635,10 +648,10 @@ public class FlexibilityControllerTest { @Test public void testExceptions_NoFlexibleConstraints() { - JobInfo.Builder jb = createJob(0); - jb.setRequiresDeviceIdle(true); - jb.setRequiresCharging(true); - jb.setRequiresBatteryNotLow(true); + JobInfo.Builder jb = createJob(0) + .setPrefersBatteryNotLow(false) + .setPrefersCharging(false) + .setPrefersDeviceIdle(false); JobStatus js = createJobStatus("testExceptions_NoFlexibleConstraints", jb); assertFalse(js.hasFlexibilityConstraint()); } @@ -697,15 +710,50 @@ public class FlexibilityControllerTest { JobStatus js = createJobStatus("testTopAppBypass", jb); synchronized (mFlexibilityController.mLock) { js.setHasAccessToUnmetered(false); - assertEquals(0, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js)); + assertEquals(0, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js)); js.setHasAccessToUnmetered(true); - assertEquals(1, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js)); + assertEquals(1, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js)); js.setHasAccessToUnmetered(false); - assertEquals(0, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js)); + assertEquals(0, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js)); } } @Test + public void testGetNumSatisfiedFlexibleConstraints() { + long nowElapsed = FROZEN_TIME; + mFlexibilityController.setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, true, nowElapsed); + mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING, true, nowElapsed); + mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, true, nowElapsed); + JobInfo.Builder jb = createJob(0) + .setPrefersBatteryNotLow(false) + .setPrefersCharging(false) + .setPrefersDeviceIdle(false); + JobStatus js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb); + assertEquals(0, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js)); + + jb = createJob(0) + .setPrefersBatteryNotLow(true) + .setPrefersCharging(false) + .setPrefersDeviceIdle(false); + js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb); + assertEquals(1, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js)); + + jb = createJob(0) + .setPrefersBatteryNotLow(true) + .setPrefersCharging(false) + .setPrefersDeviceIdle(true); + js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb); + assertEquals(2, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js)); + + jb = createJob(0) + .setPrefersBatteryNotLow(true) + .setPrefersCharging(true) + .setPrefersDeviceIdle(true); + js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb); + assertEquals(3, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js)); + } + + @Test public void testSetConstraintSatisfied_Constraints() { mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false, FROZEN_TIME); assertFalse(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE)); @@ -736,8 +784,11 @@ public class FlexibilityControllerTest { jb = createJob(i); constraints = constraintCombinations[i]; jb.setRequiresDeviceIdle((constraints & CONSTRAINT_IDLE) != 0); + jb.setPrefersDeviceIdle((constraints & CONSTRAINT_IDLE) == 0); jb.setRequiresBatteryNotLow((constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0); + jb.setPrefersBatteryNotLow((constraints & CONSTRAINT_BATTERY_NOT_LOW) == 0); jb.setRequiresCharging((constraints & CONSTRAINT_CHARGING) != 0); + jb.setPrefersCharging((constraints & CONSTRAINT_CHARGING) == 0); synchronized (mFlexibilityController.mLock) { mFlexibilityController.maybeStartTrackingJobLocked( createJobStatus(String.valueOf(i), jb), null); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java index 5dc8ed58994d..b076ab495d0c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java @@ -1039,7 +1039,9 @@ public class JobStatusTest { @Test public void testReadinessStatusWithConstraint_FlexibilityConstraint() { final JobStatus job = createJobStatus( - new JobInfo.Builder(101, new ComponentName("foo", "bar")).build()); + new JobInfo.Builder(101, new ComponentName("foo", "bar")) + .setPrefersCharging(true) + .build()); job.setConstraintSatisfied(CONSTRAINT_FLEXIBLE, sElapsedRealtimeClock.millis(), false); markImplicitConstraintsSatisfied(job, true); assertTrue(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true)); diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java index 5560b41b164f..236c74fc29e6 100644 --- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java @@ -728,6 +728,66 @@ public class JobStoreTest { } @Test + public void testPersistedPreferredBatteryNotLowConstraint() throws Exception { + JobInfo.Builder b = new Builder(8, mComponent) + .setPrefersBatteryNotLow(true) + .setPersisted(true); + JobStatus taskStatus = + JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null); + + mTaskStoreUnderTest.add(taskStatus); + waitForPendingIo(); + + final JobSet jobStatusSet = new JobSet(); + mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); + assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); + JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); + assertEquals("Battery-not-low constraint not persisted correctly.", + taskStatus.getJob().isPreferBatteryNotLow(), + loaded.getJob().isPreferBatteryNotLow()); + } + + @Test + public void testPersistedPreferredChargingConstraint() throws Exception { + JobInfo.Builder b = new Builder(8, mComponent) + .setPrefersCharging(true) + .setPersisted(true); + JobStatus taskStatus = + JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null); + + mTaskStoreUnderTest.add(taskStatus); + waitForPendingIo(); + + final JobSet jobStatusSet = new JobSet(); + mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); + assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); + JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); + assertEquals("Charging constraint not persisted correctly.", + taskStatus.getJob().isPreferCharging(), + loaded.getJob().isPreferCharging()); + } + + @Test + public void testPersistedPreferredDeviceIdleConstraint() throws Exception { + JobInfo.Builder b = new Builder(8, mComponent) + .setPrefersDeviceIdle(true) + .setPersisted(true); + JobStatus taskStatus = + JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null); + + mTaskStoreUnderTest.add(taskStatus); + waitForPendingIo(); + + final JobSet jobStatusSet = new JobSet(); + mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); + assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); + JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); + assertEquals("Idle constraint not persisted correctly.", + taskStatus.getJob().isPreferDeviceIdle(), + loaded.getJob().isPreferDeviceIdle()); + } + + @Test public void testJobWorkItems() throws Exception { JobWorkItem item1 = new JobWorkItem.Builder().build(); item1.bumpDeliveryCount(); |