From a6738a47b1f0dede8640ebf48a0bbc8a223108de Mon Sep 17 00:00:00 2001
From: Kweku Adams <kwekua@google.com>
Date: Mon, 4 Dec 2023 23:04:45 +0000
Subject: [PATCH] Make applied flex constraints configurable.

Make it possible to change which specific constraints are used in the
flex scheduling behavior.

Bug: 236261941
Bug: 299329948
Bug: 299346198
Test: atest CtsJobSchedulerTestCases:FlexibilityConstraintTest
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: I55c143d9e4ec384bd4c687dbe9313a8e78a9a6f1
---
 .../controllers/ConnectivityController.java   |   3 +-
 .../controllers/FlexibilityController.java    | 185 +++++++++-----
 .../server/job/controllers/JobStatus.java     |  25 +-
 .../FlexibilityControllerTest.java            | 236 +++++++++++++-----
 .../server/job/controllers/JobStatusTest.java |  13 +
 5 files changed, 331 insertions(+), 131 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 e06006f25d3f..f40508302ee3 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
@@ -1769,7 +1769,8 @@ public final class ConnectivityController extends RestrictingController implemen
 
     @VisibleForTesting
     class CcConfig {
-        private boolean mFlexIsEnabled = FlexibilityController.FcConfig.DEFAULT_FLEXIBILITY_ENABLED;
+        private boolean mFlexIsEnabled =
+                FlexibilityController.FcConfig.DEFAULT_APPLIED_CONSTRAINTS != 0;
         private boolean mShouldReprocessNetworkCapabilities = false;
 
         /**
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 13a474ccf451..0e67b9ac944f 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
@@ -24,7 +24,6 @@ import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 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 android.annotation.ElapsedRealtimeLong;
@@ -74,17 +73,10 @@ public final class FlexibilityController extends StateController {
     private static final int JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS = CONSTRAINT_CONNECTIVITY;
 
     /** List of all flexible constraints. */
-    private static final int FLEXIBLE_CONSTRAINTS =
+    @VisibleForTesting
+    static final int FLEXIBLE_CONSTRAINTS =
             JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS | SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
 
-    private static final int NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS =
-            Integer.bitCount(JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS);
-
-    static final int NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS =
-            Integer.bitCount(SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS);
-
-    static final int NUM_FLEXIBLE_CONSTRAINTS = Integer.bitCount(FLEXIBLE_CONSTRAINTS);
-
     private static final long NO_LIFECYCLE_END = Long.MAX_VALUE;
 
     /**
@@ -100,9 +92,15 @@ public final class FlexibilityController extends StateController {
     private long mUnseenConstraintGracePeriodMs =
             FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
 
-    @VisibleForTesting
+    /** Set of constraints supported on this device for flex scheduling. */
+    private final int mSupportedFlexConstraints;
+
     @GuardedBy("mLock")
-    boolean mFlexibilityEnabled = FcConfig.DEFAULT_FLEXIBILITY_ENABLED;
+    private boolean mFlexibilityEnabled;
+
+    /** Set of constraints that will be used in the flex policy. */
+    @GuardedBy("mLock")
+    private int mAppliedConstraints = FcConfig.DEFAULT_APPLIED_CONSTRAINTS;
 
     private long mMinTimeBetweenFlexibilityAlarmsMs =
             FcConfig.DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS;
@@ -118,9 +116,6 @@ public final class FlexibilityController extends StateController {
      */
     private int[] mPercentToDropConstraints;
 
-    @VisibleForTesting
-    boolean mDeviceSupportsFlexConstraints;
-
     /**
      * Keeps track of what flexible constraints are satisfied at the moment.
      * Is updated by the other controllers.
@@ -178,7 +173,7 @@ public final class FlexibilityController extends StateController {
                             if (!js.hasFlexibilityConstraint()) {
                                 continue;
                             }
-                            mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
+                            mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
                             mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed);
                         }
                     }
@@ -186,15 +181,23 @@ public final class FlexibilityController extends StateController {
             };
 
     private static final int MSG_UPDATE_JOBS = 0;
+    private static final int MSG_UPDATE_JOB = 1;
 
     public FlexibilityController(
             JobSchedulerService service, PrefetchController prefetchController) {
         super(service);
         mHandler = new FcHandler(AppSchedulingModuleThread.get().getLooper());
-        mDeviceSupportsFlexConstraints = !mContext.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_AUTOMOTIVE);
-        mFlexibilityEnabled &= mDeviceSupportsFlexConstraints;
-        mFlexibilityTracker = new FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS);
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+                || mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED)) {
+            // Embedded devices have no user-installable apps. Assume all jobs are critical
+            // and can't be flexed.
+            mSupportedFlexConstraints = 0;
+        } else {
+            // TODO(236261941): handle devices without a battery
+            mSupportedFlexConstraints = FLEXIBLE_CONSTRAINTS;
+        }
+        mFlexibilityEnabled = (mAppliedConstraints & mSupportedFlexConstraints) != 0;
+        mFlexibilityTracker = new FlexibilityTracker(Integer.bitCount(mSupportedFlexConstraints));
         mFcConfig = new FcConfig();
         mFlexibilityAlarmQueue = new FlexibilityAlarmQueue(
                 mContext, AppSchedulingModuleThread.get().getLooper());
@@ -218,10 +221,12 @@ public final class FlexibilityController extends StateController {
     public void maybeStartTrackingJobLocked(JobStatus js, JobStatus lastJob) {
         if (js.hasFlexibilityConstraint()) {
             final long nowElapsed = sElapsedRealtimeClock.millis();
-            if (!mDeviceSupportsFlexConstraints) {
+            if (mSupportedFlexConstraints == 0) {
                 js.setFlexibilityConstraintSatisfied(nowElapsed, true);
                 return;
             }
+            js.setNumAppliedFlexibleConstraints(
+                    Integer.bitCount(getRelevantAppliedConstraintsLocked(js)));
             js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
             mFlexibilityTracker.add(js);
             js.setTrackingController(JobStatus.TRACKING_FLEXIBILITY);
@@ -266,6 +271,14 @@ public final class FlexibilityController extends StateController {
                 || mService.isCurrentlyRunningLocked(js);
     }
 
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    int getRelevantAppliedConstraintsLocked(@NonNull JobStatus js) {
+        final int relevantConstraints = SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS
+                | (js.canApplyTransportAffinities() ? CONSTRAINT_CONNECTIVITY : 0);
+        return mAppliedConstraints & relevantConstraints;
+    }
+
     /**
      * 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
@@ -274,7 +287,7 @@ public final class FlexibilityController extends StateController {
     @VisibleForTesting
     @GuardedBy("mLock")
     boolean hasEnoughSatisfiedConstraintsLocked(@NonNull JobStatus js) {
-        final int satisfiedConstraints = mSatisfiedFlexibleConstraints
+        final int satisfiedConstraints = mSatisfiedFlexibleConstraints & mAppliedConstraints
                 & (SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS
                         | (js.areTransportAffinitiesSatisfied() ? CONSTRAINT_CONNECTIVITY : 0));
         final int numSatisfied = Integer.bitCount(satisfiedConstraints);
@@ -296,8 +309,7 @@ public final class FlexibilityController extends StateController {
         // 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));
+        final int irrelevantConstraints = ~getRelevantAppliedConstraintsLocked(js);
         for (int i = mLastSeenConstraintTimesElapsed.size() - 1; i >= 0; --i) {
             final int constraints = mLastSeenConstraintTimesElapsed.keyAt(i);
             if ((constraints & irrelevantConstraints) != 0) {
@@ -515,9 +527,9 @@ public final class FlexibilityController extends StateController {
                     for (int j = 0; j < mFlexibilityTracker.size(); j++) {
                         final ArraySet<JobStatus> jobs = mFlexibilityTracker
                                 .getJobsByNumRequiredConstraints(j);
-                        for (int i = 0; i < jobs.size(); i++) {
+                        for (int i = jobs.size() - 1; i >= 0; --i) {
                             JobStatus js = jobs.valueAt(i);
-                            mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
+                            mFlexibilityTracker.updateFlexibleConstraints(js, nowElapsed);
                             mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed);
                             if (js.setFlexibilityConstraintSatisfied(
                                     nowElapsed, isFlexibilitySatisfiedLocked(js))) {
@@ -579,18 +591,46 @@ public final class FlexibilityController extends StateController {
             mTrackedJobs.get(js.getNumRequiredFlexibleConstraints()).remove(js);
         }
 
-        public void resetJobNumDroppedConstraints(JobStatus js, long nowElapsed) {
+        /**
+         * Updates applied and dropped constraints for the job.
+         */
+        public void updateFlexibleConstraints(JobStatus js, long nowElapsed) {
+            final int prevNumRequired = js.getNumRequiredFlexibleConstraints();
+
+            final int numAppliedConstraints =
+                    Integer.bitCount(getRelevantAppliedConstraintsLocked(js));
+            js.setNumAppliedFlexibleConstraints(numAppliedConstraints);
+
             final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed);
             int toDrop = 0;
-            final int jsMaxFlexibleConstraints = NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS
-                    + (js.canApplyTransportAffinities() ? 1 : 0);
+            for (int i = 0; i < numAppliedConstraints; i++) {
+                if (curPercent >= mPercentToDropConstraints[i]) {
+                    toDrop++;
+                }
+            }
+            js.setNumDroppedFlexibleConstraints(toDrop);
+
+            if (prevNumRequired == js.getNumRequiredFlexibleConstraints()) {
+                return;
+            }
+            mTrackedJobs.get(prevNumRequired).remove(js);
+            add(js);
+        }
+
+        /**
+         * Calculates the number of constraints that should be dropped for the job, based on how
+         * far along the job is into its lifecycle.
+         */
+        public void calculateNumDroppedConstraints(JobStatus js, long nowElapsed) {
+            final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed);
+            int toDrop = 0;
+            final int jsMaxFlexibleConstraints = js.getNumAppliedFlexibleConstraints();
             for (int i = 0; i < jsMaxFlexibleConstraints; i++) {
                 if (curPercent >= mPercentToDropConstraints[i]) {
                     toDrop++;
                 }
             }
-            adjustJobsRequiredConstraints(
-                    js, js.getNumDroppedFlexibleConstraints() - toDrop, nowElapsed);
+            setNumDroppedFlexibleConstraints(js, toDrop);
         }
 
         /** Returns all tracked jobs. */
@@ -599,17 +639,14 @@ public final class FlexibilityController extends StateController {
         }
 
         /**
-         * Adjusts number of required flexible constraints and sorts it into the tracker.
-         * Returns false if the job status's number of flexible constraints is now 0.
+         * Updates the number of dropped flexible constraints and sorts it into the tracker.
          */
-        public boolean adjustJobsRequiredConstraints(JobStatus js, int adjustBy, long nowElapsed) {
-            if (adjustBy != 0) {
+        public void setNumDroppedFlexibleConstraints(JobStatus js, int numDropped) {
+            if (numDropped != js.getNumDroppedFlexibleConstraints()) {
                 remove(js);
-                js.adjustNumRequiredFlexibleConstraints(adjustBy);
-                js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
+                js.setNumDroppedFlexibleConstraints(numDropped);
                 add(js);
             }
-            return js.getNumRequiredFlexibleConstraints() > 0;
         }
 
         public int size() {
@@ -658,8 +695,10 @@ public final class FlexibilityController extends StateController {
                 if (DEBUG) {
                     Slog.d(TAG, "scheduleDropNumConstraintsAlarm: "
                             + js.getSourcePackageName() + " " + js.getSourceUserId()
+                            + " numApplied: " + js.getNumAppliedFlexibleConstraints()
                             + " numRequired: " + js.getNumRequiredFlexibleConstraints()
-                            + " numSatisfied: " + Integer.bitCount(mSatisfiedFlexibleConstraints)
+                            + " numSatisfied: " + Integer.bitCount(
+                            mSatisfiedFlexibleConstraints & getRelevantAppliedConstraintsLocked(js))
                             + " curTime: " + nowElapsed
                             + " earliest: " + earliest
                             + " latest: " + latest
@@ -669,8 +708,9 @@ public final class FlexibilityController extends StateController {
                     if (DEBUG) {
                         Slog.d(TAG, "deadline proximity met: " + js);
                     }
-                    mFlexibilityTracker.adjustJobsRequiredConstraints(js,
-                            -js.getNumRequiredFlexibleConstraints(), nowElapsed);
+                    mFlexibilityTracker.setNumDroppedFlexibleConstraints(js,
+                            js.getNumAppliedFlexibleConstraints());
+                    mHandler.obtainMessage(MSG_UPDATE_JOB, js).sendToTarget();
                     return;
                 }
                 if (nextTimeElapsed == NO_LIFECYCLE_END) {
@@ -696,12 +736,15 @@ public final class FlexibilityController extends StateController {
                 final long nowElapsed = sElapsedRealtimeClock.millis();
                 for (int i = 0; i < expired.size(); i++) {
                     JobStatus js = expired.valueAt(i);
-                    boolean wasFlexibilitySatisfied = js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE);
-
-                    if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1, nowElapsed)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Alarm fired for " + js.toShortString());
+                    }
+                    mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
+                    if (js.getNumRequiredFlexibleConstraints() > 0) {
                         scheduleDropNumConstraintsAlarm(js, nowElapsed);
                     }
-                    if (wasFlexibilitySatisfied != js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE)) {
+                    if (js.setFlexibilityConstraintSatisfied(nowElapsed,
+                            isFlexibilitySatisfiedLocked(js))) {
                         changedJobs.add(js);
                     }
                 }
@@ -725,7 +768,9 @@ public final class FlexibilityController extends StateController {
                         final long nowElapsed = sElapsedRealtimeClock.millis();
                         final ArraySet<JobStatus> changedJobs = new ArraySet<>();
 
-                        for (int o = 0; o <= NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS; ++o) {
+                        final int numAppliedSystemWideConstraints = Integer.bitCount(
+                                mAppliedConstraints & SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS);
+                        for (int o = 0; o <= numAppliedSystemWideConstraints; ++o) {
                             final ArraySet<JobStatus> jobsByNumConstraints = mFlexibilityTracker
                                     .getJobsByNumRequiredConstraints(o);
 
@@ -744,6 +789,23 @@ public final class FlexibilityController extends StateController {
                         }
                     }
                     break;
+
+                case MSG_UPDATE_JOB:
+                    synchronized (mLock) {
+                        final JobStatus js = (JobStatus) msg.obj;
+                        if (DEBUG) {
+                            Slog.d("blah", "Checking on " + js.toShortString());
+                        }
+                        final long nowElapsed = sElapsedRealtimeClock.millis();
+                        if (js.setFlexibilityConstraintSatisfied(
+                                nowElapsed, isFlexibilitySatisfiedLocked(js))) {
+                            // TODO(141645789): add method that will take a single job
+                            ArraySet<JobStatus> changedJob = new ArraySet<>();
+                            changedJob.add(js);
+                            mStateChangedListener.onControllerStateChanged(changedJob);
+                        }
+                    }
+                    break;
             }
         }
     }
@@ -754,7 +816,8 @@ public final class FlexibilityController extends StateController {
         /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
         private static final String FC_CONFIG_PREFIX = "fc_";
 
-        static final String KEY_FLEXIBILITY_ENABLED = FC_CONFIG_PREFIX + "enable_flexibility";
+        @VisibleForTesting
+        static final String KEY_APPLIED_CONSTRAINTS = FC_CONFIG_PREFIX + "applied_constraints";
         static final String KEY_DEADLINE_PROXIMITY_LIMIT =
                 FC_CONFIG_PREFIX + "flexibility_deadline_proximity_limit_ms";
         static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE =
@@ -770,7 +833,7 @@ public final class FlexibilityController extends StateController {
         static final String KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS =
                 FC_CONFIG_PREFIX + "unseen_constraint_grace_period_ms";
 
-        static final boolean DEFAULT_FLEXIBILITY_ENABLED = false;
+        static final int DEFAULT_APPLIED_CONSTRAINTS = 0;
         @VisibleForTesting
         static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
         @VisibleForTesting
@@ -783,11 +846,8 @@ public final class FlexibilityController extends StateController {
         @VisibleForTesting
         static final long DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = 3 * DAY_IN_MILLIS;
 
-        /**
-         * If false the controller will not track new jobs
-         * and the flexibility constraint will always be satisfied.
-         */
-        public boolean FLEXIBILITY_ENABLED = DEFAULT_FLEXIBILITY_ENABLED;
+        /** Which constraints to apply/consider in flex policy. */
+        public int APPLIED_CONSTRAINTS = DEFAULT_APPLIED_CONSTRAINTS;
         /** How close to a jobs' deadline all flexible constraints will be dropped. */
         public long DEADLINE_PROXIMITY_LIMIT_MS = DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS;
         /** For jobs that lack a deadline, the time that will be used to drop all constraints by. */
@@ -811,16 +871,19 @@ public final class FlexibilityController extends StateController {
         public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
                 @NonNull String key) {
             switch (key) {
-                case KEY_FLEXIBILITY_ENABLED:
-                    FLEXIBILITY_ENABLED = properties.getBoolean(key, DEFAULT_FLEXIBILITY_ENABLED)
-                            && mDeviceSupportsFlexConstraints;
-                    if (mFlexibilityEnabled != FLEXIBILITY_ENABLED) {
-                        mFlexibilityEnabled = FLEXIBILITY_ENABLED;
+                case KEY_APPLIED_CONSTRAINTS:
+                    APPLIED_CONSTRAINTS =
+                            properties.getInt(key, DEFAULT_APPLIED_CONSTRAINTS)
+                                    & mSupportedFlexConstraints;
+                    if (mAppliedConstraints != APPLIED_CONSTRAINTS) {
+                        mAppliedConstraints = APPLIED_CONSTRAINTS;
                         mShouldReevaluateConstraints = true;
-                        if (mFlexibilityEnabled) {
+                        if (mAppliedConstraints != 0) {
+                            mFlexibilityEnabled = true;
                             mPrefetchController
                                     .registerPrefetchChangedListener(mPrefetchChangedListener);
                         } else {
+                            mFlexibilityEnabled = false;
                             mPrefetchController
                                     .unRegisterPrefetchChangedListener(mPrefetchChangedListener);
                         }
@@ -893,7 +956,7 @@ public final class FlexibilityController extends StateController {
 
         private int[] parsePercentToDropString(String s) {
             String[] dropPercentString = s.split(",");
-            int[] dropPercentInt = new int[NUM_FLEXIBLE_CONSTRAINTS];
+            int[] dropPercentInt = new int[Integer.bitCount(FLEXIBLE_CONSTRAINTS)];
             if (dropPercentInt.length != dropPercentString.length) {
                 return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
             }
@@ -922,7 +985,7 @@ public final class FlexibilityController extends StateController {
             pw.println(":");
             pw.increaseIndent();
 
-            pw.print(KEY_FLEXIBILITY_ENABLED, FLEXIBILITY_ENABLED).println();
+            pw.print(KEY_APPLIED_CONSTRAINTS, APPLIED_CONSTRAINTS).println();
             pw.print(KEY_DEADLINE_PROXIMITY_LIMIT, DEADLINE_PROXIMITY_LIMIT_MS).println();
             pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE, FALLBACK_FLEXIBILITY_DEADLINE_MS).println();
             pw.print(KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS,
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 d1f575ef40c8..0d5d11e98860 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
@@ -24,7 +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;
@@ -155,7 +154,7 @@ public final class JobStatus {
     /**
      * Keeps track of how many flexible constraints must be satisfied for the job to execute.
      */
-    private final int mNumRequiredFlexibleConstraints;
+    private int mNumAppliedFlexibleConstraints;
 
     /**
      * Number of required flexible constraints that have been dropped.
@@ -697,11 +696,7 @@ public final class JobStatus {
                 && satisfiesMinWindowException
                 && (numFailures + numSystemStops) != 1
                 && lacksSomeFlexibleConstraints) {
-            mNumRequiredFlexibleConstraints =
-                    NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mCanApplyTransportAffinities ? 1 : 0);
             requiredConstraints |= CONSTRAINT_FLEXIBLE;
-        } else {
-            mNumRequiredFlexibleConstraints = 0;
         }
 
         this.requiredConstraints = requiredConstraints;
@@ -1527,9 +1522,14 @@ public final class JobStatus {
         return (requiredConstraints & CONSTRAINT_FLEXIBLE) != 0;
     }
 
+    /** Returns the number of flexible job constraints being applied to the job. */
+    public int getNumAppliedFlexibleConstraints() {
+        return mNumAppliedFlexibleConstraints;
+    }
+
     /** Returns the number of flexible job constraints required to be satisfied to execute */
     public int getNumRequiredFlexibleConstraints() {
-        return mNumRequiredFlexibleConstraints - mNumDroppedFlexibleConstraints;
+        return mNumAppliedFlexibleConstraints - mNumDroppedFlexibleConstraints;
     }
 
     /**
@@ -2112,9 +2112,14 @@ public final class JobStatus {
     }
 
     /** Adjusts the number of required flexible constraints by the given number */
-    public void adjustNumRequiredFlexibleConstraints(int adjustment) {
-        mNumDroppedFlexibleConstraints = Math.max(0, Math.min(mNumRequiredFlexibleConstraints,
-                mNumDroppedFlexibleConstraints - adjustment));
+    public void setNumAppliedFlexibleConstraints(int count) {
+        mNumAppliedFlexibleConstraints = count;
+    }
+
+    /** Sets the number of dropped flexible constraints to the given number */
+    public void setNumDroppedFlexibleConstraints(int count) {
+        mNumDroppedFlexibleConstraints = Math.max(0,
+                Math.min(mNumAppliedFlexibleConstraints, count));
     }
 
     /**
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 4fb9472021c5..650c473533ed 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
@@ -30,15 +30,16 @@ 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_APPLIED_CONSTRAINTS;
 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.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_CONTENT_TRIGGER;
 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;
@@ -66,6 +67,7 @@ import android.os.Looper;
 import android.provider.DeviceConfig;
 import android.util.ArraySet;
 
+import com.android.server.AppSchedulingModuleThread;
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.JobStore;
@@ -129,6 +131,7 @@ public class FlexibilityControllerTest {
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mPackageManager.hasSystemFeature(
                 PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(false);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_EMBEDDED)).thenReturn(false);
         // Used in FlexibilityController.FcConstants.
         doAnswer((Answer<Void>) invocationOnMock -> null)
                 .when(() -> DeviceConfig.addOnPropertiesChangedListener(
@@ -161,7 +164,8 @@ public class FlexibilityControllerTest {
 
         setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "50,60,70,80");
         setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, 0L);
-        setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS);
+        waitForQuietModuleThread();
     }
 
     @After
@@ -171,26 +175,22 @@ public class FlexibilityControllerTest {
         }
     }
 
-    private void setDeviceConfigBoolean(String key, boolean val) {
-        mDeviceConfigPropertiesBuilder.setBoolean(key, val);
-        synchronized (mFlexibilityController.mLock) {
-            mFlexibilityController.prepareForUpdatedConstantsLocked();
-            mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
-            mFlexibilityController.onConstantsUpdatedLocked();
-        }
+    private void setDeviceConfigInt(String key, int val) {
+        mDeviceConfigPropertiesBuilder.setInt(key, val);
+        updateDeviceConfig(key);
     }
 
     private void setDeviceConfigLong(String key, Long val) {
         mDeviceConfigPropertiesBuilder.setLong(key, val);
-        synchronized (mFlexibilityController.mLock) {
-            mFlexibilityController.prepareForUpdatedConstantsLocked();
-            mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
-            mFlexibilityController.onConstantsUpdatedLocked();
-        }
+        updateDeviceConfig(key);
     }
 
     private void setDeviceConfigString(String key, String val) {
         mDeviceConfigPropertiesBuilder.setString(key, val);
+        updateDeviceConfig(key);
+    }
+
+    private void updateDeviceConfig(String key) {
         synchronized (mFlexibilityController.mLock) {
             mFlexibilityController.prepareForUpdatedConstantsLocked();
             mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
@@ -198,6 +198,11 @@ public class FlexibilityControllerTest {
         }
     }
 
+    private void waitForQuietModuleThread() {
+        assertTrue("Failed to wait for quiet module thread",
+                AppSchedulingModuleThread.getHandler().runWithScissors(() -> {}, 10_000L));
+    }
+
     private static JobInfo.Builder createJob(int id) {
         return new JobInfo.Builder(id, new ComponentName("foo", "bar"));
     }
@@ -207,6 +212,10 @@ public class FlexibilityControllerTest {
         JobStatus js = JobStatus.createFromJobInfo(
                 jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, "FCTest", testTag);
         js.enqueueTime = FROZEN_TIME;
+        if (js.hasFlexibilityConstraint()) {
+            js.setNumAppliedFlexibleConstraints(Integer.bitCount(
+                    mFlexibilityController.getRelevantAppliedConstraintsLocked(js)));
+        }
         return js;
     }
 
@@ -215,18 +224,120 @@ public class FlexibilityControllerTest {
      */
     @Test
     public void testDefaultVariableValues() {
-        assertEquals(NUM_FLEXIBLE_CONSTRAINTS,
+        assertEquals(Integer.bitCount(FLEXIBLE_CONSTRAINTS),
                 mFlexibilityController.mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS.length
         );
     }
 
     @Test
-    public void testOnConstantsUpdated_DefaultFlexibility() {
+    public void testAppliedConstraints() {
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS);
+
+        // Add connectivity to require 4 constraints
+        JobStatus connJs = createJobStatus("testAppliedConstraints",
+                createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY));
+        JobStatus nonConnJs = createJobStatus("testAppliedConstraints",
+                createJob(1).setRequiredNetworkType(NETWORK_TYPE_NONE));
+
+        mFlexibilityController.maybeStartTrackingJobLocked(connJs, null);
+        mFlexibilityController.maybeStartTrackingJobLocked(nonConnJs, null);
+
+        assertEquals(4, connJs.getNumAppliedFlexibleConstraints());
+        assertEquals(3, nonConnJs.getNumAppliedFlexibleConstraints());
+
+        mFlexibilityController.setConstraintSatisfied(
+                CONSTRAINT_BATTERY_NOT_LOW, true,
+                JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS);
+        mFlexibilityController.setConstraintSatisfied(
+                CONSTRAINT_CHARGING, false,
+                JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS);
+        mFlexibilityController.setConstraintSatisfied(
+                CONSTRAINT_IDLE, false,
+                JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS);
+        mFlexibilityController.setConstraintSatisfied(
+                CONSTRAINT_CONNECTIVITY, true,
+                JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS);
+        connJs.setTransportAffinitiesSatisfied(true);
+
+        synchronized (mFlexibilityController.mLock) {
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS,
+                CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_CONNECTIVITY);
+        waitForQuietModuleThread();
+
+        // Only battery-not-low (which is satisfied) applies to the non-connectivity job, so it
+        // should be able to run.
+        assertEquals(2, connJs.getNumAppliedFlexibleConstraints());
+        assertEquals(1, nonConnJs.getNumAppliedFlexibleConstraints());
+        synchronized (mFlexibilityController.mLock) {
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, CONSTRAINT_BATTERY_NOT_LOW);
+        waitForQuietModuleThread();
+
+        assertEquals(1, connJs.getNumAppliedFlexibleConstraints());
+        assertEquals(1, nonConnJs.getNumAppliedFlexibleConstraints());
+        synchronized (mFlexibilityController.mLock) {
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, CONSTRAINT_CONNECTIVITY);
+        waitForQuietModuleThread();
+
+        // No constraints apply to the non-connectivity job, so it should be able to run.
+        assertEquals(1, connJs.getNumAppliedFlexibleConstraints());
+        assertEquals(0, nonConnJs.getNumAppliedFlexibleConstraints());
+        synchronized (mFlexibilityController.mLock) {
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, CONSTRAINT_CHARGING);
+        waitForQuietModuleThread();
+
+        assertEquals(1, connJs.getNumAppliedFlexibleConstraints());
+        assertEquals(1, nonConnJs.getNumAppliedFlexibleConstraints());
+        synchronized (mFlexibilityController.mLock) {
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, 0);
+        waitForQuietModuleThread();
+
+        // No constraints apply, so they should be able to run.
+        assertEquals(0, connJs.getNumAppliedFlexibleConstraints());
+        assertEquals(0, nonConnJs.getNumAppliedFlexibleConstraints());
+        synchronized (mFlexibilityController.mLock) {
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+
+        // Invalid constraint to apply.
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, CONSTRAINT_CONTENT_TRIGGER);
+        waitForQuietModuleThread();
+
+        // No constraints apply, so they should be able to run.
+        assertEquals(0, connJs.getNumAppliedFlexibleConstraints());
+        assertEquals(0, nonConnJs.getNumAppliedFlexibleConstraints());
+        synchronized (mFlexibilityController.mLock) {
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+    }
+
+    @Test
+    public void testOnConstantsUpdated_AppliedConstraints() {
         JobStatus js = createJobStatus("testDefaultFlexibilityConfig", createJob(0));
-        assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
-        setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, false);
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, 0);
         assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
-        setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS);
         assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
     }
 
@@ -261,10 +372,10 @@ public class FlexibilityControllerTest {
                 new int[] {10, 20, 30, 40});
         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10,
                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(1);
         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 2,
                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(2);
         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 3,
                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
     }
@@ -303,12 +414,12 @@ public class FlexibilityControllerTest {
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
                 nextTimeToDropNumConstraints);
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(1);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 6,
                 nextTimeToDropNumConstraints);
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(2);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 7,
@@ -321,11 +432,11 @@ public class FlexibilityControllerTest {
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(130400100, nextTimeToDropNumConstraints);
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(1);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(156320100L, nextTimeToDropNumConstraints);
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(2);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(182240100L, nextTimeToDropNumConstraints);
@@ -337,11 +448,11 @@ public class FlexibilityControllerTest {
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(129600100, nextTimeToDropNumConstraints);
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(1);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(155520100L, nextTimeToDropNumConstraints);
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(2);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(181440100L, nextTimeToDropNumConstraints);
@@ -357,12 +468,12 @@ public class FlexibilityControllerTest {
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
                 nextTimeToDropNumConstraints);
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(1);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 6,
                 nextTimeToDropNumConstraints);
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(2);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 7,
@@ -580,10 +691,10 @@ public class FlexibilityControllerTest {
     }
 
     @Test
-    public void testWontStopJobFromRunning() {
-        JobStatus js = createJobStatus("testWontStopJobFromRunning", createJob(101));
+    public void testWontStopAlreadyRunningJob() {
+        JobStatus js = createJobStatus("testWontStopAlreadyRunningJob", createJob(101));
         // Stop satisfied constraints from causing a false positive.
-        js.adjustNumRequiredFlexibleConstraints(100);
+        js.setNumAppliedFlexibleConstraints(100);
         synchronized (mFlexibilityController.mLock) {
             when(mJobSchedulerService.isCurrentlyRunningLocked(js)).thenReturn(true);
             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
@@ -593,10 +704,9 @@ public class FlexibilityControllerTest {
     @Test
     public void testFlexibilityTracker() {
         FlexibilityController.FlexibilityTracker flexTracker =
-                mFlexibilityController.new
-                        FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS);
+                mFlexibilityController.new FlexibilityTracker(4);
         // Plus one for jobs with 0 required constraint.
-        assertEquals(NUM_FLEXIBLE_CONSTRAINTS + 1, flexTracker.size());
+        assertEquals(4 + 1, flexTracker.size());
         JobStatus[] jobs = new JobStatus[4];
         JobInfo.Builder jb;
         for (int i = 0; i < jobs.length; i++) {
@@ -622,21 +732,21 @@ public class FlexibilityControllerTest {
             assertEquals(3, trackedJobs.get(3).size());
             assertEquals(0, trackedJobs.get(4).size());
 
-            flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
+            flexTracker.setNumDroppedFlexibleConstraints(jobs[0], 1);
             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(0, trackedJobs.get(4).size());
 
-            flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
+            flexTracker.setNumDroppedFlexibleConstraints(jobs[0], 2);
             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(0, trackedJobs.get(4).size());
 
-            flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
+            flexTracker.setNumDroppedFlexibleConstraints(jobs[0], 3);
             assertEquals(2, trackedJobs.get(0).size());
             assertEquals(0, trackedJobs.get(1).size());
             assertEquals(0, trackedJobs.get(2).size());
@@ -650,14 +760,14 @@ public class FlexibilityControllerTest {
             assertEquals(1, trackedJobs.get(3).size());
             assertEquals(0, trackedJobs.get(4).size());
 
-            flexTracker.resetJobNumDroppedConstraints(jobs[0], FROZEN_TIME);
+            flexTracker.calculateNumDroppedConstraints(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);
+            flexTracker.setNumDroppedFlexibleConstraints(jobs[0], 2);
             assertEquals(1, trackedJobs.get(0).size());
             assertEquals(1, trackedJobs.get(1).size());
             assertEquals(0, trackedJobs.get(2).size());
@@ -669,7 +779,7 @@ public class FlexibilityControllerTest {
             JobSchedulerService.sElapsedRealtimeClock =
                     Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
-            flexTracker.resetJobNumDroppedConstraints(jobs[0], nowElapsed);
+            flexTracker.calculateNumDroppedConstraints(jobs[0], nowElapsed);
             assertEquals(1, trackedJobs.get(0).size());
             assertEquals(0, trackedJobs.get(1).size());
             assertEquals(1, trackedJobs.get(2).size());
@@ -779,9 +889,9 @@ public class FlexibilityControllerTest {
         mFlexibilityController.setConstraintSatisfied(
                 SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS, false, FROZEN_TIME);
         // Require only a single constraint
-        jsAny.adjustNumRequiredFlexibleConstraints(-3);
-        jsCell.adjustNumRequiredFlexibleConstraints(-2);
-        jsWifi.adjustNumRequiredFlexibleConstraints(-2);
+        jsAny.setNumAppliedFlexibleConstraints(1);
+        jsCell.setNumAppliedFlexibleConstraints(1);
+        jsWifi.setNumAppliedFlexibleConstraints(1);
         synchronized (mFlexibilityController.mLock) {
             jsAny.setTransportAffinitiesSatisfied(false);
             jsCell.setTransportAffinitiesSatisfied(false);
@@ -1008,9 +1118,9 @@ public class FlexibilityControllerTest {
     }
 
     @Test
-    public void testResetJobNumDroppedConstraints() {
+    public void testCalculateNumDroppedConstraints() {
         JobInfo.Builder jb = createJob(22);
-        JobStatus js = createJobStatus("testResetJobNumDroppedConstraints", jb);
+        JobStatus js = createJobStatus("testCalculateNumDroppedConstraints", jb);
         long nowElapsed = FROZEN_TIME;
 
         mFlexibilityController.mFlexibilityTracker.add(js);
@@ -1025,14 +1135,14 @@ public class FlexibilityControllerTest {
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
         mFlexibilityController.mFlexibilityTracker
-                .adjustJobsRequiredConstraints(js, -1, nowElapsed);
+                .setNumDroppedFlexibleConstraints(js, 1);
 
         assertEquals(2, js.getNumRequiredFlexibleConstraints());
         assertEquals(1, js.getNumDroppedFlexibleConstraints());
         assertEquals(1, mFlexibilityController
                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
 
-        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
 
         assertEquals(2, js.getNumRequiredFlexibleConstraints());
         assertEquals(1, js.getNumDroppedFlexibleConstraints());
@@ -1043,7 +1153,7 @@ public class FlexibilityControllerTest {
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
-        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
 
         assertEquals(3, js.getNumRequiredFlexibleConstraints());
         assertEquals(0, js.getNumDroppedFlexibleConstraints());
@@ -1054,7 +1164,7 @@ public class FlexibilityControllerTest {
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
-        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
 
         assertEquals(0, js.getNumRequiredFlexibleConstraints());
         assertEquals(3, js.getNumDroppedFlexibleConstraints());
@@ -1063,7 +1173,7 @@ public class FlexibilityControllerTest {
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
-        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
 
         assertEquals(1, js.getNumRequiredFlexibleConstraints());
         assertEquals(2, js.getNumDroppedFlexibleConstraints());
@@ -1139,20 +1249,28 @@ public class FlexibilityControllerTest {
     }
 
     @Test
-    public void testDeviceDisabledFlexibility_Auto() {
-        when(mPackageManager.hasSystemFeature(
-                PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(true);
+    public void testUnsupportedDevice_Auto() {
+        runTestUnsupportedDevice(PackageManager.FEATURE_AUTOMOTIVE);
+    }
+
+    @Test
+    public void testUnsupportedDevice_Embedded() {
+        runTestUnsupportedDevice(PackageManager.FEATURE_EMBEDDED);
+    }
+
+    private void runTestUnsupportedDevice(String feature) {
+        when(mPackageManager.hasSystemFeature(feature)).thenReturn(true);
         mFlexibilityController =
                 new FlexibilityController(mJobSchedulerService, mPrefetchController);
-        assertFalse(mFlexibilityController.mFlexibilityEnabled);
+        assertFalse(mFlexibilityController.isEnabled());
 
-        JobStatus js = createJobStatus("testIsAuto", createJob(0));
+        JobStatus js = createJobStatus("testUnsupportedDevice", createJob(0));
 
         mFlexibilityController.maybeStartTrackingJobLocked(js, null);
         assertTrue(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
 
-        setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
-        assertFalse(mFlexibilityController.mFlexibilityEnabled);
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS);
+        assertFalse(mFlexibilityController.isEnabled());
 
         ArrayList<ArraySet<JobStatus>> jobs =
                 mFlexibilityController.mFlexibilityTracker.getArrayList();
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 8397b87706d6..293391f43828 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
@@ -231,6 +231,19 @@ public class JobStatusTest {
         assertTrue(job.canRunInDoze());
     }
 
+    @Test
+    public void testFlexibleConstraintCounts() {
+        JobStatus js = createJobStatus(new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+                .setUserInitiated(false)
+                .build());
+
+        js.setNumAppliedFlexibleConstraints(3);
+        js.setNumDroppedFlexibleConstraints(2);
+        assertEquals(3, js.getNumAppliedFlexibleConstraints());
+        assertEquals(2, js.getNumDroppedFlexibleConstraints());
+        assertEquals(1, js.getNumRequiredFlexibleConstraints());
+    }
+
     @Test
     public void testIsUserVisibleJob() {
         JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
-- 
GitLab