Add methods to mark some constraints as optional.

These methods will let apps mark battery-not-low, charging,
and/or idle as optional (but preferred) instead of just as a
hard requirement. This gives JobScheduler more freedom to defer
the job to more ideal times if it can without confusing developers
further.

For now, the methods are hidden because there's a little bit more work
to be done on the communication and documentation.

Bug: 236261941
Test: atest CtsJobSchedulerTestCases:FlexibilityConstraintTest
Test: atest CtsJobSchedulerTestCases:JobInfoTest
Test: atest CtsJobSchedulerTestCases:JobSchedulingTest
Test: atest frameworks/base/services/tests/mockingservicestests/src/com/android/server/job
Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/job
Change-Id: Ia27947b3a40f59d0278698e3d12a6b656b18aa16
This commit is contained in:
Kweku Adams
2022-12-21 20:28:17 +00:00
parent aa24b1bdc5
commit eb72459a89
9 changed files with 362 additions and 67 deletions

View File

@@ -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;
@@ -521,6 +522,30 @@ public class JobInfo implements Parcelable {
return ((flags & FLAG_EXEMPT_FROM_APP_STANDBY) != 0) && !isPeriodic();
}
/**
* @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)
*/
@@ -556,6 +581,13 @@ public class JobInfo implements Parcelable {
return constraintFlags;
}
/**
* @hide
*/
public int getPreferredConstraintFlags() {
return mPreferredConstraintFlags;
}
/**
* Which content: URIs must change for the job to be scheduled. Returns null
* if there are none required.
@@ -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");
}

View File

@@ -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));
}
/**

View File

@@ -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));
}

View File

@@ -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

View File

@@ -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();

View File

@@ -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());

View File

@@ -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(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(1).size());
assertEquals(1, 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], -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(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(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(0, trackedJobs.get(2).size());
assertEquals(2, 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(0, trackedJobs.get(2).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(2, trackedJobs.get(1).size());
assertEquals(0, trackedJobs.get(2).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,14 +710,49 @@ 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);
@@ -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);

View File

@@ -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));

View File

@@ -727,6 +727,66 @@ public class JobStoreTest {
taskStatus.getJob().isRequireBatteryNotLow());
}
@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();