diff options
4 files changed, 507 insertions, 86 deletions
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 0cf0cc5dcd22..e06006f25d3f 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 @@ -640,22 +640,27 @@ public final class ConnectivityController extends RestrictingController implemen if (mCcConfig.mShouldReprocessNetworkCapabilities || (mFlexibilityController.isEnabled() != mCcConfig.mFlexIsEnabled)) { AppSchedulingModuleThread.getHandler().post(() -> { - boolean shouldUpdateJobs = false; - for (int i = 0; i < mAvailableNetworks.size(); ++i) { - CachedNetworkMetadata metadata = mAvailableNetworks.valueAt(i); - if (metadata == null || metadata.networkCapabilities == null) { - continue; + boolean flexAffinitiesChanged = false; + boolean flexAffinitiesSatisfied = false; + synchronized (mLock) { + for (int i = 0; i < mAvailableNetworks.size(); ++i) { + CachedNetworkMetadata metadata = mAvailableNetworks.valueAt(i); + if (metadata == null) { + continue; + } + if (updateTransportAffinitySatisfaction(metadata)) { + // Something changed. Update jobs. + flexAffinitiesChanged = true; + } + flexAffinitiesSatisfied |= metadata.satisfiesTransportAffinities; } - boolean satisfies = satisfiesTransportAffinities(metadata.networkCapabilities); - if (metadata.satisfiesTransportAffinities != satisfies) { - metadata.satisfiesTransportAffinities = satisfies; - // Something changed. Update jobs. - shouldUpdateJobs = true; + if (flexAffinitiesChanged) { + mFlexibilityController.setConstraintSatisfied( + JobStatus.CONSTRAINT_CONNECTIVITY, + flexAffinitiesSatisfied, sElapsedRealtimeClock.millis()); + updateAllTrackedJobsLocked(false); } } - if (shouldUpdateJobs) { - updateAllTrackedJobsLocked(false); - } }); } } @@ -1059,6 +1064,22 @@ public final class ConnectivityController extends RestrictingController implemen return false; } + /** + * Updates {@link CachedNetworkMetadata#satisfiesTransportAffinities} in the given + * {@link CachedNetworkMetadata} object. + * @return true if the satisfaction changed + */ + private boolean updateTransportAffinitySatisfaction( + @NonNull CachedNetworkMetadata cachedNetworkMetadata) { + final boolean satisfiesAffinities = + satisfiesTransportAffinities(cachedNetworkMetadata.networkCapabilities); + if (cachedNetworkMetadata.satisfiesTransportAffinities != satisfiesAffinities) { + cachedNetworkMetadata.satisfiesTransportAffinities = satisfiesAffinities; + return true; + } + return false; + } + private boolean satisfiesTransportAffinities(@Nullable NetworkCapabilities capabilities) { if (!mFlexibilityController.isEnabled()) { return true; @@ -1552,7 +1573,9 @@ public final class ConnectivityController extends RestrictingController implemen } } cnm.networkCapabilities = capabilities; - cnm.satisfiesTransportAffinities = satisfiesTransportAffinities(capabilities); + if (updateTransportAffinitySatisfaction(cnm)) { + maybeUpdateFlexConstraintLocked(cnm); + } maybeRegisterSignalStrengthCallbackLocked(capabilities); updateTrackedJobsLocked(-1, network); postAdjustCallbacks(); @@ -1566,8 +1589,13 @@ public final class ConnectivityController extends RestrictingController implemen } synchronized (mLock) { final CachedNetworkMetadata cnm = mAvailableNetworks.remove(network); - if (cnm != null && cnm.networkCapabilities != null) { - maybeUnregisterSignalStrengthCallbackLocked(cnm.networkCapabilities); + if (cnm != null) { + if (cnm.networkCapabilities != null) { + maybeUnregisterSignalStrengthCallbackLocked(cnm.networkCapabilities); + } + if (cnm.satisfiesTransportAffinities) { + maybeUpdateFlexConstraintLocked(null); + } } for (int u = 0; u < mCurrentDefaultNetworkCallbacks.size(); ++u) { UidDefaultNetworkCallback callback = mCurrentDefaultNetworkCallbacks.valueAt(u); @@ -1639,6 +1667,37 @@ public final class ConnectivityController extends RestrictingController implemen } } } + + /** + * Maybe call {@link FlexibilityController#setConstraintSatisfied(int, boolean, long)} + * if the network affinity state has changed. + */ + @GuardedBy("mLock") + private void maybeUpdateFlexConstraintLocked( + @Nullable CachedNetworkMetadata cachedNetworkMetadata) { + if (cachedNetworkMetadata != null + && cachedNetworkMetadata.satisfiesTransportAffinities) { + mFlexibilityController.setConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY, + true, sElapsedRealtimeClock.millis()); + } else { + // This network doesn't satisfy transport affinities. Check if any other + // available networks do satisfy the affinities before saying that the + // transport affinity is no longer satisfied for flex. + boolean isTransportAffinitySatisfied = false; + for (int i = mAvailableNetworks.size() - 1; i >= 0; --i) { + final CachedNetworkMetadata cnm = mAvailableNetworks.valueAt(i); + if (cnm != null && cnm.satisfiesTransportAffinities) { + isTransportAffinitySatisfied = true; + break; + } + } + if (!isTransportAffinitySatisfied) { + mFlexibilityController.setConstraintSatisfied( + JobStatus.CONSTRAINT_CONNECTIVITY, false, + sElapsedRealtimeClock.millis()); + } + } + } }; private final INetworkPolicyListener mNetPolicyListener = new NetworkPolicyManager.Listener() { 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 70f9a52f3299..fed3c42ab87f 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 @@ -43,6 +43,8 @@ import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; import android.util.SparseArrayMap; +import android.util.SparseLongArray; +import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -68,11 +70,6 @@ public final class FlexibilityController extends StateController { | CONSTRAINT_CHARGING | CONSTRAINT_IDLE; - /** List of flexible constraints a job can opt into. */ - static final int OPTIONAL_FLEXIBLE_CONSTRAINTS = CONSTRAINT_BATTERY_NOT_LOW - | CONSTRAINT_CHARGING - | CONSTRAINT_IDLE; - /** List of all job flexible constraints whose satisfaction is job specific. */ private static final int JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS = CONSTRAINT_CONNECTIVITY; @@ -83,9 +80,6 @@ public final class FlexibilityController extends StateController { private static final int NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS = Integer.bitCount(JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS); - static final int NUM_OPTIONAL_FLEXIBLE_CONSTRAINTS = - Integer.bitCount(OPTIONAL_FLEXIBLE_CONSTRAINTS); - static final int NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS = Integer.bitCount(SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS); @@ -103,6 +97,9 @@ public final class FlexibilityController extends StateController { private long mRescheduledJobDeadline = FcConfig.DEFAULT_RESCHEDULED_JOB_DEADLINE_MS; private long mMaxRescheduledDeadline = FcConfig.DEFAULT_MAX_RESCHEDULED_DEADLINE_MS; + private long mUnseenConstraintGracePeriodMs = + FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS; + @VisibleForTesting @GuardedBy("mLock") boolean mFlexibilityEnabled = FcConfig.DEFAULT_FLEXIBILITY_ENABLED; @@ -132,6 +129,9 @@ public final class FlexibilityController extends StateController { @GuardedBy("mLock") int mSatisfiedFlexibleConstraints; + @GuardedBy("mLock") + private final SparseLongArray mLastSeenConstraintTimesElapsed = new SparseLongArray(); + @VisibleForTesting @GuardedBy("mLock") final FlexibilityTracker mFlexibilityTracker; @@ -258,25 +258,68 @@ public final class FlexibilityController extends StateController { boolean isFlexibilitySatisfiedLocked(JobStatus js) { return !mFlexibilityEnabled || mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP - || getNumSatisfiedRequiredConstraintsLocked(js) - >= js.getNumRequiredFlexibleConstraints() + || hasEnoughSatisfiedConstraintsLocked(js) || mService.isCurrentlyRunningLocked(js); } + /** + * Returns whether there are enough constraints satisfied to allow running the job from flex's + * perspective. This takes into account unseen constraint combinations and expectations around + * whether additional constraints can ever be satisfied. + */ @VisibleForTesting @GuardedBy("mLock") - int getNumSatisfiedRequiredConstraintsLocked(JobStatus js) { - return Integer.bitCount(mSatisfiedFlexibleConstraints) - // Connectivity is job-specific, so must be handled separately. - + (js.canApplyTransportAffinities() - && js.areTransportAffinitiesSatisfied() ? 1 : 0); + boolean hasEnoughSatisfiedConstraintsLocked(@NonNull JobStatus js) { + final int satisfiedConstraints = mSatisfiedFlexibleConstraints + & (SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + | (js.areTransportAffinitiesSatisfied() ? CONSTRAINT_CONNECTIVITY : 0)); + final int numSatisfied = Integer.bitCount(satisfiedConstraints); + if (numSatisfied >= js.getNumRequiredFlexibleConstraints()) { + return true; + } + // We don't yet have the full number of required flex constraints. See if we should expect + // to be able to reach it. If not, then there's no point waiting anymore. + final long nowElapsed = sElapsedRealtimeClock.millis(); + if (nowElapsed < mUnseenConstraintGracePeriodMs) { + // Too soon after boot. Not enough time to start predicting. Wait longer. + return false; + } + + // The intention is to not force jobs to wait for constraint combinations that have never + // been seen together in a while. The job may still be allowed to wait for other constraint + // combinations. Thus, the logic is: + // If all the constraint combinations that have a count higher than the current satisfied + // count have not been seen recently enough, then assume they won't be seen anytime soon, + // so don't force the job to wait longer. If any combinations with a higher count have been + // seen recently, then the job can potentially wait for those combinations. + final int irrelevantConstraints = ~(SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + | (js.canApplyTransportAffinities() ? CONSTRAINT_CONNECTIVITY : 0)); + for (int i = mLastSeenConstraintTimesElapsed.size() - 1; i >= 0; --i) { + final int constraints = mLastSeenConstraintTimesElapsed.keyAt(i); + if ((constraints & irrelevantConstraints) != 0) { + // Ignore combinations that couldn't satisfy this job's needs. + continue; + } + final long lastSeenElapsed = mLastSeenConstraintTimesElapsed.valueAt(i); + final boolean seenRecently = + nowElapsed - lastSeenElapsed <= mUnseenConstraintGracePeriodMs; + if (Integer.bitCount(constraints) > numSatisfied && seenRecently) { + // We've seen a set of constraints with a higher count than what is currently + // satisfied recently enough, which means we can expect to see it again at some + // point. Keep waiting for now. + return false; + } + } + + // We haven't seen any constraint set with more satisfied than the current satisfied count. + // There's no reason to expect additional constraints to be satisfied. Let the job run. + return true; } /** * Sets the controller's constraint to a given state. * Changes flexibility constraint satisfaction for affected jobs. */ - @VisibleForTesting void setConstraintSatisfied(int constraint, boolean state, long nowElapsed) { synchronized (mLock) { final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0; @@ -286,14 +329,34 @@ public final class FlexibilityController extends StateController { if (DEBUG) { Slog.d(TAG, "setConstraintSatisfied: " - + " constraint: " + constraint + " state: " + state); + + " constraint: " + constraint + " state: " + state); + } + + // Mark now as the last time we saw this set of constraints. + mLastSeenConstraintTimesElapsed.put(mSatisfiedFlexibleConstraints, nowElapsed); + if (!state) { + // Mark now as the last time we saw this particular constraint. + // (Good for logging/dump purposes). + mLastSeenConstraintTimesElapsed.put(constraint, nowElapsed); } mSatisfiedFlexibleConstraints = (mSatisfiedFlexibleConstraints & ~constraint) | (state ? constraint : 0); - // Push the job update to the handler to avoid blocking other controllers and - // potentially batch back-to-back controller state updates together. - mHandler.obtainMessage(MSG_UPDATE_JOBS).sendToTarget(); + + if ((JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS & constraint) != 0) { + // Job-specific constraint --> don't need to proceed with logic below that + // works with system-wide constraints. + return; + } + + if (mFlexibilityEnabled) { + // Only attempt to update jobs if the flex logic is enabled. Otherwise, the status + // of the jobs won't change, so all the work will be a waste. + + // Push the job update to the handler to avoid blocking other controllers and + // potentially batch back-to-back controller state updates together. + mHandler.obtainMessage(MSG_UPDATE_JOBS).sendToTarget(); + } } } @@ -543,7 +606,6 @@ public final class FlexibilityController extends StateController { if (!predicate.test(js)) { continue; } - pw.print("#"); js.printUniqueId(pw); pw.print(" from "); UserHandle.formatUid(pw, js.getSourceUid()); @@ -645,7 +707,7 @@ public final class FlexibilityController extends StateController { final long nowElapsed = sElapsedRealtimeClock.millis(); final ArraySet<JobStatus> changedJobs = new ArraySet<>(); - for (int o = 0; o <= NUM_OPTIONAL_FLEXIBLE_CONSTRAINTS; ++o) { + for (int o = 0; o <= NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS; ++o) { final ArraySet<JobStatus> jobsByNumConstraints = mFlexibilityTracker .getJobsByNumRequiredConstraints(o); @@ -687,6 +749,8 @@ public final class FlexibilityController extends StateController { FC_CONFIG_PREFIX + "max_rescheduled_deadline_ms"; static final String KEY_RESCHEDULED_JOB_DEADLINE_MS = FC_CONFIG_PREFIX + "rescheduled_job_deadline_ms"; + static final String KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = + FC_CONFIG_PREFIX + "unseen_constraint_grace_period_ms"; static final boolean DEFAULT_FLEXIBILITY_ENABLED = false; @VisibleForTesting @@ -698,6 +762,8 @@ public final class FlexibilityController extends StateController { final int[] DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS = {50, 60, 70, 80}; private static final long DEFAULT_RESCHEDULED_JOB_DEADLINE_MS = HOUR_IN_MILLIS; private static final long DEFAULT_MAX_RESCHEDULED_DEADLINE_MS = 5 * DAY_IN_MILLIS; + @VisibleForTesting + static final long DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = 3 * DAY_IN_MILLIS; /** * If false the controller will not track new jobs @@ -717,6 +783,11 @@ public final class FlexibilityController extends StateController { public long RESCHEDULED_JOB_DEADLINE_MS = DEFAULT_RESCHEDULED_JOB_DEADLINE_MS; /** The max deadline for rescheduled jobs. */ public long MAX_RESCHEDULED_DEADLINE_MS = DEFAULT_MAX_RESCHEDULED_DEADLINE_MS; + /** + * How long to wait after last seeing a constraint combination before no longer waiting for + * it in order to run jobs. + */ + public long UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS; @GuardedBy("mLock") public void processConstantLocked(@NonNull DeviceConfig.Properties properties, @@ -780,6 +851,14 @@ public final class FlexibilityController extends StateController { mShouldReevaluateConstraints = true; } break; + case KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS: + UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = + properties.getLong(key, DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS); + if (mUnseenConstraintGracePeriodMs != UNSEEN_CONSTRAINT_GRACE_PERIOD_MS) { + mUnseenConstraintGracePeriodMs = UNSEEN_CONSTRAINT_GRACE_PERIOD_MS; + mShouldReevaluateConstraints = true; + } + break; case KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS: String dropPercentString = properties.getString(key, ""); PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS = @@ -834,6 +913,8 @@ public final class FlexibilityController extends StateController { PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS).println(); pw.print(KEY_RESCHEDULED_JOB_DEADLINE_MS, RESCHEDULED_JOB_DEADLINE_MS).println(); pw.print(KEY_MAX_RESCHEDULED_DEADLINE_MS, MAX_RESCHEDULED_DEADLINE_MS).println(); + pw.print(KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS, UNSEEN_CONSTRAINT_GRACE_PERIOD_MS) + .println(); pw.decreaseIndent(); } @@ -854,12 +935,34 @@ public final class FlexibilityController extends StateController { @Override @GuardedBy("mLock") public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { - pw.println("# Constraints Satisfied: " + Integer.bitCount(mSatisfiedFlexibleConstraints)); - pw.print("Satisfied Flexible Constraints: "); + pw.print("Satisfied Flexible Constraints:"); JobStatus.dumpConstraints(pw, mSatisfiedFlexibleConstraints); pw.println(); pw.println(); + final long nowElapsed = sElapsedRealtimeClock.millis(); + pw.println("Time since constraint combos last seen:"); + pw.increaseIndent(); + for (int i = 0; i < mLastSeenConstraintTimesElapsed.size(); ++i) { + final int constraints = mLastSeenConstraintTimesElapsed.keyAt(i); + if (constraints == mSatisfiedFlexibleConstraints) { + pw.print("0ms"); + } else { + TimeUtils.formatDuration( + mLastSeenConstraintTimesElapsed.valueAt(i), nowElapsed, pw); + } + pw.print(":"); + if (constraints != 0) { + // dumpConstraints prepends with a space, so no need to add a space after the : + JobStatus.dumpConstraints(pw, constraints); + } else { + pw.print(" none"); + } + pw.println(); + } + pw.decreaseIndent(); + + pw.println(); mFlexibilityTracker.dump(pw, predicate); pw.println(); mFlexibilityAlarmQueue.dump(pw); 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 10f8510c7c70..4958f1c1c214 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 @@ -44,6 +44,7 @@ import static com.android.server.job.controllers.ConnectivityController.CcConfig import static com.android.server.job.controllers.ConnectivityController.TRANSPORT_AFFINITY_AVOID; import static com.android.server.job.controllers.ConnectivityController.TRANSPORT_AFFINITY_PREFER; import static com.android.server.job.controllers.ConnectivityController.TRANSPORT_AFFINITY_UNDEFINED; +import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -51,6 +52,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; @@ -989,6 +991,7 @@ public class ConnectivityControllerTest { final ConnectivityController controller = new ConnectivityController(mService, mFlexibilityController); + InOrder flexControllerInOrder = inOrder(mFlexibilityController); final Network meteredNet = mock(Network.class); final NetworkCapabilities meteredCaps = createCapabilitiesBuilder().build(); @@ -1042,10 +1045,13 @@ public class ConnectivityControllerTest { answerNetwork(generalCallback, redCallback, null, null, null); answerNetwork(generalCallback, blueCallback, null, null, null); - assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + flexControllerInOrder.verify(mFlexibilityController, never()) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), anyBoolean(), anyLong()); + + assertFalse(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(red2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(blue2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); assertFalse(red.areTransportAffinitiesSatisfied()); assertFalse(blue.areTransportAffinitiesSatisfied()); assertFalse(red2.areTransportAffinitiesSatisfied()); @@ -1059,13 +1065,15 @@ public class ConnectivityControllerTest { generalCallback.onCapabilitiesChanged(meteredNet, meteredCaps); - assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertFalse(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(red2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(blue2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); // No transport is specified. Accept the network for transport affinity. setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false); controller.onConstantsUpdatedLocked(); + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(true), anyLong()); assertFalse(red.areTransportAffinitiesSatisfied()); assertTrue(blue.areTransportAffinitiesSatisfied()); assertFalse(red2.areTransportAffinitiesSatisfied()); @@ -1073,6 +1081,8 @@ public class ConnectivityControllerTest { // No transport is specified. Avoid the network for transport affinity. setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, true); controller.onConstantsUpdatedLocked(); + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(false), anyLong()); assertFalse(red.areTransportAffinitiesSatisfied()); assertFalse(blue.areTransportAffinitiesSatisfied()); assertFalse(red2.areTransportAffinitiesSatisfied()); @@ -1086,12 +1096,17 @@ public class ConnectivityControllerTest { generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredCaps); - assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + flexControllerInOrder.verify(mFlexibilityController, never()) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), anyBoolean(), anyLong()); + + assertFalse(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); // No transport is specified. Accept the network for transport affinity. setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false); controller.onConstantsUpdatedLocked(); + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(true), anyLong()); assertFalse(red.areTransportAffinitiesSatisfied()); assertTrue(blue.areTransportAffinitiesSatisfied()); assertFalse(red2.areTransportAffinitiesSatisfied()); @@ -1099,6 +1114,8 @@ public class ConnectivityControllerTest { // No transport is specified. Avoid the network for transport affinity. setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, true); controller.onConstantsUpdatedLocked(); + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(false), anyLong()); assertFalse(red.areTransportAffinitiesSatisfied()); assertFalse(blue.areTransportAffinitiesSatisfied()); assertFalse(red2.areTransportAffinitiesSatisfied()); @@ -1112,8 +1129,13 @@ public class ConnectivityControllerTest { generalCallback.onLost(meteredNet); - assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + // Only the metered network is lost. The unmetered network still satisfies the + // affinities. + flexControllerInOrder.verify(mFlexibilityController, never()) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), anyBoolean(), anyLong()); + + assertTrue(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); } // Specific UID was blocked @@ -1123,8 +1145,12 @@ public class ConnectivityControllerTest { generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredCaps); - assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + // No change + flexControllerInOrder.verify(mFlexibilityController, never()) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), anyBoolean(), anyLong()); + + assertFalse(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); } // Metered wifi @@ -1134,10 +1160,13 @@ public class ConnectivityControllerTest { generalCallback.onCapabilitiesChanged(meteredNet, meteredWifiCaps); - assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(true), anyLong()); + + assertFalse(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(red2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertTrue(blue2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); // Wifi is preferred. setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false); @@ -1164,10 +1193,14 @@ public class ConnectivityControllerTest { generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredCelullarCaps); - assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + // Metered network still has wifi transport + flexControllerInOrder.verify(mFlexibilityController, never()) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(false), anyLong()); + + assertTrue(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertTrue(red2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(blue2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); // Cellular is avoided. setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false); @@ -1185,6 +1218,14 @@ public class ConnectivityControllerTest { assertFalse(blue2.areTransportAffinitiesSatisfied()); } + // Remove wifi transport + { + generalCallback.onCapabilitiesChanged(meteredNet, meteredCaps); + + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(false), anyLong()); + } + // Undefined affinity final NetworkCapabilities unmeteredTestCaps = createCapabilitiesBuilder() .addCapability(NET_CAPABILITY_NOT_METERED) @@ -1198,14 +1239,16 @@ public class ConnectivityControllerTest { generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredTestCaps); - assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertTrue(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(red2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(blue2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); // Undefined is preferred. setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false); controller.onConstantsUpdatedLocked(); + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(true), anyLong()); assertTrue(red.areTransportAffinitiesSatisfied()); assertTrue(blue.areTransportAffinitiesSatisfied()); assertFalse(red2.areTransportAffinitiesSatisfied()); @@ -1213,6 +1256,46 @@ public class ConnectivityControllerTest { // Undefined is avoided. setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, true); controller.onConstantsUpdatedLocked(); + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(false), anyLong()); + assertFalse(red.areTransportAffinitiesSatisfied()); + assertFalse(blue.areTransportAffinitiesSatisfied()); + assertFalse(red2.areTransportAffinitiesSatisfied()); + assertFalse(blue2.areTransportAffinitiesSatisfied()); + } + + // Lost all networks + { + // Set network as accepted to help confirm onLost notifies flex controller + setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false); + controller.onConstantsUpdatedLocked(); + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(true), anyLong()); + + answerNetwork(generalCallback, redCallback, unmeteredNet, null, null); + answerNetwork(generalCallback, blueCallback, unmeteredNet, null, null); + + generalCallback.onLost(meteredNet); + generalCallback.onLost(unmeteredNet); + + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(false), anyLong()); + + assertFalse(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false); + controller.onConstantsUpdatedLocked(); + flexControllerInOrder.verify(mFlexibilityController, never()) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), anyBoolean(), anyLong()); + assertFalse(red.areTransportAffinitiesSatisfied()); + assertFalse(blue.areTransportAffinitiesSatisfied()); + assertFalse(red2.areTransportAffinitiesSatisfied()); + assertFalse(blue2.areTransportAffinitiesSatisfied()); + // Undefined is avoided. + setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, true); + controller.onConstantsUpdatedLocked(); + flexControllerInOrder.verify(mFlexibilityController, never()) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), anyBoolean(), anyLong()); assertFalse(red.areTransportAffinitiesSatisfied()); assertFalse(blue.areTransportAffinitiesSatisfied()); assertFalse(red2.areTransportAffinitiesSatisfied()); @@ -1275,7 +1358,7 @@ public class ConnectivityControllerTest { final ConnectivityController controller = spy( new ConnectivityController(mService, mFlexibilityController)); doReturn(true).when(controller) - .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY)); + .wouldBeReadyWithConstraintLocked(any(), eq(CONSTRAINT_CONNECTIVITY)); doReturn(true).when(controller).isNetworkAvailable(any()); final JobStatus red = createJobStatus(createJob() .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) @@ -1318,7 +1401,7 @@ public class ConnectivityControllerTest { final ConnectivityController controller = spy( new ConnectivityController(mService, mFlexibilityController)); doReturn(false).when(controller) - .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY)); + .wouldBeReadyWithConstraintLocked(any(), eq(CONSTRAINT_CONNECTIVITY)); final JobStatus red = createJobStatus(createJob() .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED); @@ -1388,7 +1471,7 @@ public class ConnectivityControllerTest { // Both jobs would still be ready. Exception should not be revoked. doReturn(true).when(controller) - .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY)); + .wouldBeReadyWithConstraintLocked(any(), eq(CONSTRAINT_CONNECTIVITY)); doReturn(true).when(controller).isNetworkAvailable(any()); controller.reevaluateStateLocked(UID_RED); inOrder.verify(mNetPolicyManagerInternal, never()) @@ -1396,9 +1479,9 @@ public class ConnectivityControllerTest { // One job is still ready. Exception should not be revoked. doReturn(true).when(controller).wouldBeReadyWithConstraintLocked( - eq(redOne), eq(JobStatus.CONSTRAINT_CONNECTIVITY)); + eq(redOne), eq(CONSTRAINT_CONNECTIVITY)); doReturn(false).when(controller).wouldBeReadyWithConstraintLocked( - eq(redTwo), eq(JobStatus.CONSTRAINT_CONNECTIVITY)); + eq(redTwo), eq(CONSTRAINT_CONNECTIVITY)); controller.reevaluateStateLocked(UID_RED); inOrder.verify(mNetPolicyManagerInternal, never()) .setAppIdleWhitelist(eq(UID_RED), anyBoolean()); @@ -1406,7 +1489,7 @@ public class ConnectivityControllerTest { // Both jobs are not ready. Exception should be revoked. doReturn(false).when(controller) - .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY)); + .wouldBeReadyWithConstraintLocked(any(), eq(CONSTRAINT_CONNECTIVITY)); controller.reevaluateStateLocked(UID_RED); inOrder.verify(mNetPolicyManagerInternal, times(1)) .setAppIdleWhitelist(eq(UID_RED), eq(false)); @@ -1473,26 +1556,26 @@ public class ConnectivityControllerTest { controller.maybeStartTrackingJobLocked(unnetworked, null); answerNetwork(callback.getValue(), redCallback.getValue(), null, cellularNet, cellularCaps); - assertTrue(networked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(unnetworked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertTrue(networked.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(unnetworked.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); networked.setStandbyBucket(RESTRICTED_INDEX); unnetworked.setStandbyBucket(RESTRICTED_INDEX); controller.startTrackingRestrictedJobLocked(networked); controller.startTrackingRestrictedJobLocked(unnetworked); - assertFalse(networked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertFalse(networked.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); // Unnetworked shouldn't be affected by ConnectivityController since it doesn't have a // connectivity constraint. - assertFalse(unnetworked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertFalse(unnetworked.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); networked.setStandbyBucket(RARE_INDEX); unnetworked.setStandbyBucket(RARE_INDEX); controller.stopTrackingRestrictedJobLocked(networked); controller.stopTrackingRestrictedJobLocked(unnetworked); - assertTrue(networked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertTrue(networked.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); // Unnetworked shouldn't be affected by ConnectivityController since it doesn't have a // connectivity constraint. - assertFalse(unnetworked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertFalse(unnetworked.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); } @Test 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 ee68b6d0e546..0659f7e9a064 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 @@ -19,19 +19,25 @@ package com.android.server.job.controllers; import static android.app.job.JobInfo.BIAS_FOREGROUND_SERVICE; import static android.app.job.JobInfo.BIAS_TOP_APP; import static android.app.job.JobInfo.NETWORK_TYPE_ANY; +import static android.app.job.JobInfo.NETWORK_TYPE_CELLULAR; +import static android.app.job.JobInfo.NETWORK_TYPE_NONE; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS; +import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS; import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_DEADLINE_PROXIMITY_LIMIT; import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE; import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FLEXIBILITY_ENABLED; import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS; import static com.android.server.job.controllers.FlexibilityController.NUM_FLEXIBLE_CONSTRAINTS; +import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING; +import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE; import static com.android.server.job.controllers.JobStatus.MIN_WINDOW_FOR_FLEXIBILITY_MS; @@ -54,6 +60,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; +import android.net.NetworkRequest; import android.os.Looper; import android.provider.DeviceConfig; import android.util.ArraySet; @@ -693,15 +700,59 @@ public class FlexibilityControllerTest { @Test public void testTransportAffinity() { - JobInfo.Builder jb = createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY); - JobStatus js = createJobStatus("testTopAppBypass", jb); + JobStatus jsAny = createJobStatus("testTransportAffinity", + createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY)); + JobStatus jsCell = createJobStatus("testTransportAffinity", + createJob(0).setRequiredNetworkType(NETWORK_TYPE_CELLULAR)); + JobStatus jsWifi = createJobStatus("testTransportAffinity", + createJob(0).setRequiredNetwork( + new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI) + .build())); + // Disable the unseen constraint logic. + mFlexibilityController.setConstraintSatisfied( + SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS, true, FROZEN_TIME); + mFlexibilityController.setConstraintSatisfied( + SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS, false, FROZEN_TIME); + // Require only a single constraint + jsAny.adjustNumRequiredFlexibleConstraints(-3); + jsCell.adjustNumRequiredFlexibleConstraints(-2); + jsWifi.adjustNumRequiredFlexibleConstraints(-2); synchronized (mFlexibilityController.mLock) { - js.setTransportAffinitiesSatisfied(false); - assertEquals(0, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js)); - js.setTransportAffinitiesSatisfied(true); - assertEquals(1, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js)); - js.setTransportAffinitiesSatisfied(false); - assertEquals(0, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js)); + jsAny.setTransportAffinitiesSatisfied(false); + jsCell.setTransportAffinitiesSatisfied(false); + jsWifi.setTransportAffinitiesSatisfied(false); + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_CONNECTIVITY, false, FROZEN_TIME); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsAny)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsCell)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsWifi)); + + // A good network exists, but the network hasn't been assigned to any of the jobs + jsAny.setTransportAffinitiesSatisfied(false); + jsCell.setTransportAffinitiesSatisfied(false); + jsWifi.setTransportAffinitiesSatisfied(false); + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_CONNECTIVITY, true, FROZEN_TIME); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsAny)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsCell)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsWifi)); + + // The good network has been assigned to the relevant jobs + jsAny.setTransportAffinitiesSatisfied(true); + jsCell.setTransportAffinitiesSatisfied(false); + jsWifi.setTransportAffinitiesSatisfied(true); + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsAny)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsCell)); + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsWifi)); + + // One job loses access to the network. + jsAny.setTransportAffinitiesSatisfied(true); + jsCell.setTransportAffinitiesSatisfied(false); + jsWifi.setTransportAffinitiesSatisfied(false); + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsAny)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsCell)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsWifi)); } } @@ -768,6 +819,131 @@ public class FlexibilityControllerTest { } @Test + public void testHasEnoughSatisfiedConstraints_unseenConstraints_soonAfterBoot() { + // Add connectivity to require 4 constraints + JobStatus js = createJobStatus("testHasEnoughSatisfiedConstraints", + createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY)); + + // Too soon after boot + JobSchedulerService.sElapsedRealtimeClock = + Clock.fixed(Instant.ofEpochMilli(100 - 1), ZoneOffset.UTC); + synchronized (mFlexibilityController.mLock) { + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(js)); + } + JobSchedulerService.sElapsedRealtimeClock = + Clock.fixed(Instant.ofEpochMilli(DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS - 1), + ZoneOffset.UTC); + synchronized (mFlexibilityController.mLock) { + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(js)); + } + + // Long after boot + + // No constraints ever seen. Don't bother waiting + JobSchedulerService.sElapsedRealtimeClock = + Clock.fixed(Instant.ofEpochMilli(DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS), + ZoneOffset.UTC); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(js)); + } + } + + @Test + public void testHasEnoughSatisfiedConstraints_unseenConstraints_longAfterBoot() { + // Add connectivity to require 4 constraints + JobStatus connJs = createJobStatus("testHasEnoughSatisfiedConstraints", + createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY)); + JobStatus nonConnJs = createJobStatus("testHasEnoughSatisfiedConstraints", + createJob(0).setRequiredNetworkType(NETWORK_TYPE_NONE)); + + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_BATTERY_NOT_LOW, true, + 2 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10); + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_CHARGING, true, + 3 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10); + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_IDLE, true, + 4 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10); + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_CONNECTIVITY, true, + 5 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10); + + // Long after boot + // All constraints satisfied right now + JobSchedulerService.sElapsedRealtimeClock = + Clock.fixed(Instant.ofEpochMilli(DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS), + ZoneOffset.UTC); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs)); + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs)); + } + + // Go down to 2 satisfied + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_CONNECTIVITY, false, + 6 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10); + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_IDLE, false, + 7 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10); + // 3 & 4 constraints were seen recently enough, so the job should wait + synchronized (mFlexibilityController.mLock) { + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs)); + } + + // 4 constraints still in the grace period. Wait. + JobSchedulerService.sElapsedRealtimeClock = + Clock.fixed( + Instant.ofEpochMilli(16 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10), + ZoneOffset.UTC); + synchronized (mFlexibilityController.mLock) { + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs)); + } + + // 3 constraints still in the grace period. Wait. + JobSchedulerService.sElapsedRealtimeClock = + Clock.fixed( + Instant.ofEpochMilli(17 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10), + ZoneOffset.UTC); + synchronized (mFlexibilityController.mLock) { + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs)); + } + + // 3 constraints haven't been seen recently. Don't wait. + JobSchedulerService.sElapsedRealtimeClock = + Clock.fixed( + Instant.ofEpochMilli( + 17 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10 + 1), + ZoneOffset.UTC); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs)); + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs)); + } + + // Add then remove connectivity. Resets expectation of 3 constraints for connectivity jobs. + // Connectivity job should wait while the non-connectivity job can run. + // of getting back to 4 constraints. + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_CONNECTIVITY, true, + 18 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10); + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_CONNECTIVITY, false, + 19 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10); + JobSchedulerService.sElapsedRealtimeClock = + Clock.fixed( + Instant.ofEpochMilli( + 19 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10 + 1), + ZoneOffset.UTC); + synchronized (mFlexibilityController.mLock) { + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs)); + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs)); + } + } + + @Test public void testResetJobNumDroppedConstraints() { JobInfo.Builder jb = createJob(22); JobStatus js = createJobStatus("testResetJobNumDroppedConstraints", jb); |