summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2023-03-17 02:27:09 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-03-17 02:27:09 +0000
commit93d5b7930d69e4245ff97ffe64d1a8b252b3d50a (patch)
treeeb36022549da4e3e118792a0b134160ce5e92421
parent2d55867d9aa92e71561f11235b00306c811da695 (diff)
parenteb72459a8994180ddc1c9ee05d15ac31413ee2ea (diff)
Merge "Add methods to mark some constraints as optional." into udc-dev
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobInfo.java174
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobStore.java16
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java5
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java8
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java36
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java111
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/job/JobStoreTest.java60
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();