Introduce concept of work types.

In preparation for dedicated Contexts for expedited jobs, introduce the
concept of work types. For now, this is just a refactor, so we only have
TOP and BG types. The refactoring also helps make it clearer how many
contexts are reserved for each type, though the numbers aren't changed
as part of the refactor.

Bug: 141645789
Bug: 171305774
Bug: 178119369
Test: atest CtsJobSchedulerTestCases
Test: atest FrameworksMockingServicesTests:JobSchedulerServiceTest
Test: atest FrameworksServicesTests:PrioritySchedulingTest
Test: atest FrameworksServicesTests:WorkCountTrackerTest
Test: atest FrameworksServicesTests:WorkTypeConfigTest
Change-Id: Ib6682cb16ef3944e1439d865400f8d48bf1d6890
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 7cad4ab..e05f0b0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -16,6 +16,8 @@
 
 package com.android.server.job;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.job.JobInfo;
 import android.content.BroadcastReceiver;
@@ -26,8 +28,11 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.provider.DeviceConfig;
+import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
+import android.util.Pair;
 import android.util.Slog;
+import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
@@ -36,16 +41,17 @@
 import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.util.StatLogger;
 import com.android.server.JobSchedulerBackgroundThread;
-import com.android.server.job.JobSchedulerService.MaxJobCountsPerMemoryTrimLevel;
 import com.android.server.job.controllers.JobStatus;
 import com.android.server.job.controllers.StateController;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Iterator;
 import java.util.List;
 
 /**
- * This class decides, given the various configuration and the system status, how many more jobs
- * can start.
+ * This class decides, given the various configuration and the system status, which jobs can start
+ * and which {@link JobServiceContext} to run each job on.
  */
 class JobConcurrencyManager {
     private static final String TAG = JobSchedulerService.TAG;
@@ -56,9 +62,22 @@
             CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms";
     private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000;
 
+    static final int WORK_TYPE_NONE = 0;
+    static final int WORK_TYPE_TOP = 1 << 0;
+    static final int WORK_TYPE_BG = 1 << 1;
+    private static final int NUM_WORK_TYPES = 2;
+
+    @IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = {
+            WORK_TYPE_NONE,
+            WORK_TYPE_TOP,
+            WORK_TYPE_BG
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface WorkType {
+    }
+
     private final Object mLock;
     private final JobSchedulerService mService;
-    private final JobSchedulerService.Constants mConstants;
     private final Context mContext;
     private final Handler mHandler;
 
@@ -72,6 +91,53 @@
 
     private static final int MAX_JOB_CONTEXTS_COUNT = JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
 
+    private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_ON =
+            new WorkConfigLimitsPerMemoryTrimLevel(
+                    new WorkTypeConfig("screen_on_normal", 8,
+                            // defaultMin
+                            List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 2)),
+                            // defaultMax
+                            List.of(Pair.create(WORK_TYPE_BG, 6))),
+                    new WorkTypeConfig("screen_on_moderate", 8,
+                            // defaultMin
+                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)),
+                            // defaultMax
+                            List.of(Pair.create(WORK_TYPE_BG, 4))),
+                    new WorkTypeConfig("screen_on_low", 5,
+                            // defaultMin
+                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
+                            // defaultMax
+                            List.of(Pair.create(WORK_TYPE_BG, 1))),
+                    new WorkTypeConfig("screen_on_critical", 5,
+                            // defaultMin
+                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
+                            // defaultMax
+                            List.of(Pair.create(WORK_TYPE_BG, 1)))
+            );
+    private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_OFF =
+            new WorkConfigLimitsPerMemoryTrimLevel(
+                    new WorkTypeConfig("screen_off_normal", 10,
+                            // defaultMin
+                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)),
+                            // defaultMax
+                            List.of(Pair.create(WORK_TYPE_BG, 6))),
+                    new WorkTypeConfig("screen_off_moderate", 10,
+                            // defaultMin
+                            List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 2)),
+                            // defaultMax
+                            List.of(Pair.create(WORK_TYPE_BG, 4))),
+                    new WorkTypeConfig("screen_off_low", 5,
+                            // defaultMin
+                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
+                            // defaultMax
+                            List.of(Pair.create(WORK_TYPE_BG, 1))),
+                    new WorkTypeConfig("screen_off_critical", 5,
+                            // defaultMin
+                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
+                            // defaultMax
+                            List.of(Pair.create(WORK_TYPE_BG, 1)))
+            );
+
     /**
      * This array essentially stores the state of mActiveServices array.
      * The ith index stores the job present on the ith JobServiceContext.
@@ -84,10 +150,11 @@
 
     int[] mRecycledPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
 
-    /** Max job counts according to the current system state. */
-    private JobSchedulerService.MaxJobCounts mMaxJobCounts;
+    int[] mRecycledWorkTypeForContext = new int[MAX_JOB_CONTEXTS_COUNT];
 
-    private final JobCountTracker mJobCountTracker = new JobCountTracker();
+    private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>();
+
+    private final WorkCountTracker mWorkCountTracker = new WorkCountTracker();
 
     /** Wait for this long after screen off before adjusting the job concurrency. */
     private long mScreenOffAdjustmentDelayMs = DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS;
@@ -114,7 +181,6 @@
     JobConcurrencyManager(JobSchedulerService service) {
         mService = service;
         mLock = mService.mLock;
-        mConstants = service.mConstants;
         mContext = service.getContext();
 
         mHandler = JobSchedulerBackgroundThread.getHandler();
@@ -209,12 +275,6 @@
         }
     }
 
-    private boolean isFgJob(JobStatus job) {
-        // (It's super confusing PRIORITY_BOUND_FOREGROUND_SERVICE isn't FG here)
-        return job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP
-                || job.shouldTreatAsExpeditedJob();
-    }
-
     @GuardedBy("mLock")
     private void refreshSystemStateLocked() {
         final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis();
@@ -237,28 +297,29 @@
     }
 
     @GuardedBy("mLock")
-    private void updateMaxCountsLocked() {
+    private void updateCounterConfigLocked() {
         refreshSystemStateLocked();
 
-        final MaxJobCountsPerMemoryTrimLevel jobCounts = mEffectiveInteractiveState
-                ? mConstants.MAX_JOB_COUNTS_SCREEN_ON
-                : mConstants.MAX_JOB_COUNTS_SCREEN_OFF;
+        final WorkConfigLimitsPerMemoryTrimLevel workConfigs = mEffectiveInteractiveState
+                ? CONFIG_LIMITS_SCREEN_ON : CONFIG_LIMITS_SCREEN_OFF;
 
-
+        WorkTypeConfig workTypeConfig;
         switch (mLastMemoryTrimLevel) {
             case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
-                mMaxJobCounts = jobCounts.moderate;
+                workTypeConfig = workConfigs.moderate;
                 break;
             case ProcessStats.ADJ_MEM_FACTOR_LOW:
-                mMaxJobCounts = jobCounts.low;
+                workTypeConfig = workConfigs.low;
                 break;
             case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
-                mMaxJobCounts = jobCounts.critical;
+                workTypeConfig = workConfigs.critical;
                 break;
             default:
-                mMaxJobCounts = jobCounts.normal;
+                workTypeConfig = workConfigs.normal;
                 break;
         }
+
+        mWorkCountTracker.setConfig(workTypeConfig);
     }
 
     /**
@@ -282,31 +343,26 @@
             Slog.d(TAG, printPendingQueueLocked());
         }
 
-        final JobPackageTracker tracker = mService.mJobPackageTracker;
         final List<JobStatus> pendingJobs = mService.mPendingJobs;
         final List<JobServiceContext> activeServices = mService.mActiveServices;
-        final List<StateController> controllers = mService.mControllers;
-
-        updateMaxCountsLocked();
 
         // To avoid GC churn, we recycle the arrays.
         JobStatus[] contextIdToJobMap = mRecycledAssignContextIdToJobMap;
         boolean[] slotChanged = mRecycledSlotChanged;
         int[] preferredUidForContext = mRecycledPreferredUidForContext;
+        int[] workTypeForContext = mRecycledWorkTypeForContext;
 
+        updateCounterConfigLocked();
+        // Reset everything since we'll re-evaluate the current state.
+        mWorkCountTracker.resetCounts();
 
-        // Initialize the work variables and also count running jobs.
-        mJobCountTracker.reset(
-                mMaxJobCounts.getMaxTotal(),
-                mMaxJobCounts.getMaxBg(),
-                mMaxJobCounts.getMinBg());
-
-        for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
+        for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
             final JobServiceContext js = mService.mActiveServices.get(i);
             final JobStatus status = js.getRunningJobLocked();
 
             if ((contextIdToJobMap[i] = status) != null) {
-                mJobCountTracker.incrementRunningJobCount(isFgJob(status));
+                mWorkCountTracker.incrementRunningJobCount(js.getRunningJobWorkType());
+                workTypeForContext[i] = js.getRunningJobWorkType();
             }
 
             slotChanged[i] = false;
@@ -317,41 +373,25 @@
         }
 
         // Next, update the job priorities, and also count the pending FG / BG jobs.
-        for (int i = 0; i < pendingJobs.size(); i++) {
-            final JobStatus pending = pendingJobs.get(i);
+        updateNonRunningPriorities(pendingJobs, true);
 
-            // If job is already running, go to next job.
-            int jobRunningContext = findJobContextIdFromMap(pending, contextIdToJobMap);
-            if (jobRunningContext != -1) {
-                continue;
-            }
-
-            final int priority = mService.evaluateJobPriorityLocked(pending);
-            pending.lastEvaluatedPriority = priority;
-
-            mJobCountTracker.incrementPendingJobCount(isFgJob(pending));
-        }
-
-        mJobCountTracker.onCountDone();
+        mWorkCountTracker.onCountDone();
 
         for (int i = 0; i < pendingJobs.size(); i++) {
             final JobStatus nextPending = pendingJobs.get(i);
 
-            // Unfortunately we need to repeat this relatively expensive check.
-            int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
-            if (jobRunningContext != -1) {
+            if (mRunningJobs.contains(nextPending)) {
                 continue;
             }
 
             // TODO(171305774): make sure HPJs aren't pre-empted and add dedicated contexts for them
 
-            final boolean isPendingFg = isFgJob(nextPending);
-
             // Find an available slot for nextPending. The context should be available OR
             // it should have lowest priority among all running jobs
             // (sharing the same Uid as nextPending)
             int minPriorityForPreemption = Integer.MAX_VALUE;
             int selectedContextId = -1;
+            int workType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
             boolean startingJob = false;
             for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
                 JobStatus job = contextIdToJobMap[j];
@@ -360,7 +400,7 @@
                     final boolean preferredUidOkay = (preferredUid == nextPending.getUid())
                             || (preferredUid == JobServiceContext.NO_PREFERRED_UID);
 
-                    if (preferredUidOkay && mJobCountTracker.canJobStart(isPendingFg)) {
+                    if (preferredUidOkay && workType != WORK_TYPE_NONE) {
                         // This slot is free, and we haven't yet hit the limit on
                         // concurrent jobs...  we can just throw the job in to here.
                         selectedContextId = j;
@@ -396,19 +436,19 @@
             }
             if (startingJob) {
                 // Increase the counters when we're going to start a job.
-                mJobCountTracker.onStartingNewJob(isPendingFg);
+                workTypeForContext[selectedContextId] = workType;
+                mWorkCountTracker.stageJob(workType);
             }
         }
         if (DEBUG) {
             Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
         }
 
-        mJobCountTracker.logStatus();
+        if (DEBUG) {
+            Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString());
+        }
 
-        tracker.noteConcurrency(mJobCountTracker.getTotalRunningJobCountToNote(),
-                mJobCountTracker.getFgRunningJobCountToNote());
-
-        for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
+        for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
             boolean preservePreferredUid = false;
             if (slotChanged[i]) {
                 JobStatus js = activeServices.get(i).getRunningJobLocked();
@@ -426,30 +466,58 @@
                         Slog.d(TAG, "About to run job on context "
                                 + i + ", job: " + pendingJob);
                     }
-                    for (int ic=0; ic<controllers.size(); ic++) {
-                        controllers.get(ic).prepareForExecutionLocked(pendingJob);
-                    }
-                    if (!activeServices.get(i).executeRunnableJob(pendingJob)) {
-                        Slog.d(TAG, "Error executing " + pendingJob);
-                    }
-                    if (pendingJobs.remove(pendingJob)) {
-                        tracker.noteNonpending(pendingJob);
-                    }
+                    startJobLocked(activeServices.get(i), pendingJob, workTypeForContext[i]);
                 }
             }
             if (!preservePreferredUid) {
                 activeServices.get(i).clearPreferredUid();
             }
         }
+        mWorkCountTracker.resetStagingCount();
+        noteConcurrency();
     }
 
-    private static int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
-        for (int i=0; i<map.length; i++) {
-            if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
-                return i;
+    private void noteConcurrency() {
+        mService.mJobPackageTracker.noteConcurrency(mRunningJobs.size(),
+                // TODO: log per type instead of only TOP
+                mWorkCountTracker.getRunningJobCount(WORK_TYPE_TOP));
+    }
+
+    private void updateNonRunningPriorities(@NonNull final List<JobStatus> pendingJobs,
+            boolean updateCounter) {
+        for (int i = 0; i < pendingJobs.size(); i++) {
+            final JobStatus pending = pendingJobs.get(i);
+
+            // If job is already running, go to next job.
+            if (mRunningJobs.contains(pending)) {
+                continue;
+            }
+
+            pending.lastEvaluatedPriority = mService.evaluateJobPriorityLocked(pending);
+
+            if (updateCounter) {
+                mWorkCountTracker.incrementPendingJobCount(getJobWorkTypes(pending));
             }
         }
-        return -1;
+    }
+
+    private void startJobLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus,
+            @WorkType final int workType) {
+        final List<StateController> controllers = mService.mControllers;
+        for (int ic = 0; ic < controllers.size(); ic++) {
+            controllers.get(ic).prepareForExecutionLocked(jobStatus);
+        }
+        if (!worker.executeRunnableJob(jobStatus, workType)) {
+            Slog.e(TAG, "Error executing " + jobStatus);
+            mWorkCountTracker.onStagedJobFailed(workType);
+        } else {
+            mRunningJobs.add(jobStatus);
+            mWorkCountTracker.onJobStarted(workType);
+        }
+        final List<JobStatus> pendingJobs = mService.mPendingJobs;
+        if (pendingJobs.remove(jobStatus)) {
+            mService.mJobPackageTracker.noteNonpending(jobStatus);
+        }
     }
 
     @GuardedBy("mLock")
@@ -484,6 +552,16 @@
 
         mScreenOffAdjustmentDelayMs = properties.getLong(
                 KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS);
+
+        CONFIG_LIMITS_SCREEN_ON.normal.update(properties);
+        CONFIG_LIMITS_SCREEN_ON.moderate.update(properties);
+        CONFIG_LIMITS_SCREEN_ON.low.update(properties);
+        CONFIG_LIMITS_SCREEN_ON.critical.update(properties);
+
+        CONFIG_LIMITS_SCREEN_OFF.normal.update(properties);
+        CONFIG_LIMITS_SCREEN_OFF.moderate.update(properties);
+        CONFIG_LIMITS_SCREEN_OFF.low.update(properties);
+        CONFIG_LIMITS_SCREEN_OFF.critical.update(properties);
     }
 
     public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) {
@@ -494,6 +572,14 @@
             pw.print("Configuration:");
             pw.increaseIndent();
             pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println();
+            CONFIG_LIMITS_SCREEN_ON.normal.dump(pw);
+            CONFIG_LIMITS_SCREEN_ON.moderate.dump(pw);
+            CONFIG_LIMITS_SCREEN_ON.low.dump(pw);
+            CONFIG_LIMITS_SCREEN_ON.critical.dump(pw);
+            CONFIG_LIMITS_SCREEN_OFF.normal.dump(pw);
+            CONFIG_LIMITS_SCREEN_OFF.moderate.dump(pw);
+            CONFIG_LIMITS_SCREEN_OFF.low.dump(pw);
+            CONFIG_LIMITS_SCREEN_OFF.critical.dump(pw);
             pw.decreaseIndent();
 
             pw.print("Screen state: current ");
@@ -514,7 +600,7 @@
 
             pw.println("Current max jobs:");
             pw.println("  ");
-            pw.println(mJobCountTracker);
+            pw.println(mWorkCountTracker);
 
             pw.println();
 
@@ -540,8 +626,6 @@
         proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS,
                 nowRealtime - mLastScreenOffRealtime);
 
-        mJobCountTracker.dumpProto(proto, JobConcurrencyManagerProto.JOB_COUNT_TRACKER);
-
         proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL, mLastMemoryTrimLevel);
 
         mStatLogger.dumpProto(proto, JobConcurrencyManagerProto.STATS);
@@ -549,199 +633,312 @@
         proto.end(token);
     }
 
+    int getJobWorkTypes(@NonNull JobStatus js) {
+        int classification = 0;
+        // TODO(171305774): create dedicated work type for EJ and FGS
+        if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP
+                || js.shouldTreatAsExpeditedJob()) {
+            classification |= WORK_TYPE_TOP;
+        } else {
+            classification |= WORK_TYPE_BG;
+        }
+        return classification;
+    }
+
+    @VisibleForTesting
+    static class WorkTypeConfig {
+        private static final String KEY_PREFIX_MAX_TOTAL =
+                CONFIG_KEY_PREFIX_CONCURRENCY + "max_total_";
+        private static final String KEY_PREFIX_MAX_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "max_top_";
+        private static final String KEY_PREFIX_MAX_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "max_bg_";
+        private static final String KEY_PREFIX_MIN_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "min_top_";
+        private static final String KEY_PREFIX_MIN_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "min_bg_";
+        private final String mConfigIdentifier;
+
+        private int mMaxTotal;
+        private final SparseIntArray mMinReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
+        private final SparseIntArray mMaxAllowedSlots = new SparseIntArray(NUM_WORK_TYPES);
+        private final int mDefaultMaxTotal;
+        private final SparseIntArray mDefaultMinReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
+        private final SparseIntArray mDefaultMaxAllowedSlots = new SparseIntArray(NUM_WORK_TYPES);
+
+        WorkTypeConfig(@NonNull String configIdentifier, int defaultMaxTotal,
+                List<Pair<Integer, Integer>> defaultMin, List<Pair<Integer, Integer>> defaultMax) {
+            mConfigIdentifier = configIdentifier;
+            mDefaultMaxTotal = mMaxTotal = defaultMaxTotal;
+            for (int i = defaultMin.size() - 1; i >= 0; --i) {
+                mDefaultMinReservedSlots.put(defaultMin.get(i).first, defaultMin.get(i).second);
+            }
+            for (int i = defaultMax.size() - 1; i >= 0; --i) {
+                mDefaultMaxAllowedSlots.put(defaultMax.get(i).first, defaultMax.get(i).second);
+            }
+            update(new DeviceConfig.Properties.Builder(
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER).build());
+        }
+
+        void update(@NonNull DeviceConfig.Properties properties) {
+            // Ensure total in the range [1, MAX_JOB_CONTEXTS_COUNT].
+            mMaxTotal = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT,
+                    properties.getInt(KEY_PREFIX_MAX_TOTAL + mConfigIdentifier, mDefaultMaxTotal)));
+
+            mMaxAllowedSlots.clear();
+            // Ensure they're in the range [1, total].
+            final int maxTop = Math.max(1, Math.min(mMaxTotal,
+                    properties.getInt(KEY_PREFIX_MAX_TOP + mConfigIdentifier,
+                            mDefaultMaxAllowedSlots.get(WORK_TYPE_TOP, mMaxTotal))));
+            mMaxAllowedSlots.put(WORK_TYPE_TOP, maxTop);
+            final int maxBg = Math.max(1, Math.min(mMaxTotal,
+                    properties.getInt(KEY_PREFIX_MAX_BG + mConfigIdentifier,
+                            mDefaultMaxAllowedSlots.get(WORK_TYPE_BG, mMaxTotal))));
+            mMaxAllowedSlots.put(WORK_TYPE_BG, maxBg);
+
+            int remaining = mMaxTotal;
+            mMinReservedSlots.clear();
+            // Ensure top is in the range [1, min(maxTop, total)]
+            final int minTop = Math.max(1, Math.min(Math.min(maxTop, mMaxTotal),
+                    properties.getInt(KEY_PREFIX_MIN_TOP + mConfigIdentifier,
+                            mDefaultMinReservedSlots.get(WORK_TYPE_TOP))));
+            mMinReservedSlots.put(WORK_TYPE_TOP, minTop);
+            remaining -= minTop;
+            // Ensure bg is in the range [0, min(maxBg, remaining)]
+            final int minBg = Math.max(0, Math.min(Math.min(maxBg, remaining),
+                    properties.getInt(KEY_PREFIX_MIN_BG + mConfigIdentifier,
+                            mDefaultMinReservedSlots.get(WORK_TYPE_BG))));
+            mMinReservedSlots.put(WORK_TYPE_BG, minBg);
+        }
+
+        int getMaxTotal() {
+            return mMaxTotal;
+        }
+
+        int getMax(@WorkType int workType) {
+            return mMaxAllowedSlots.get(workType, mMaxTotal);
+        }
+
+        int getMinReserved(@WorkType int workType) {
+            return mMinReservedSlots.get(workType);
+        }
+
+        void dump(IndentingPrintWriter pw) {
+            pw.print(KEY_PREFIX_MAX_TOTAL + mConfigIdentifier, mMaxTotal).println();
+            pw.print(KEY_PREFIX_MIN_TOP + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_TOP))
+                    .println();
+            pw.print(KEY_PREFIX_MAX_TOP + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_TOP))
+                    .println();
+            pw.print(KEY_PREFIX_MIN_BG + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_BG))
+                    .println();
+            pw.print(KEY_PREFIX_MAX_BG + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_BG))
+                    .println();
+        }
+    }
+
+    /** {@link WorkTypeConfig} for each memory trim level. */
+    static class WorkConfigLimitsPerMemoryTrimLevel {
+        public final WorkTypeConfig normal;
+        public final WorkTypeConfig moderate;
+        public final WorkTypeConfig low;
+        public final WorkTypeConfig critical;
+
+        WorkConfigLimitsPerMemoryTrimLevel(WorkTypeConfig normal, WorkTypeConfig moderate,
+                WorkTypeConfig low, WorkTypeConfig critical) {
+            this.normal = normal;
+            this.moderate = moderate;
+            this.low = low;
+            this.critical = critical;
+        }
+    }
+
     /**
-     * This class decides, taking into account {@link #mMaxJobCounts} and how mny jos are running /
-     * pending, how many more job can start.
+     * This class decides, taking into account the current {@link WorkTypeConfig} and how many jobs
+     * are running/pending, how many more job can start.
      *
      * Extracted for testing and logging.
      */
     @VisibleForTesting
-    static class JobCountTracker {
-        private int mConfigNumMaxTotalJobs;
-        private int mConfigNumMaxBgJobs;
-        private int mConfigNumMinBgJobs;
+    static class WorkCountTracker {
+        private int mConfigMaxTotal;
+        private final SparseIntArray mConfigNumReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
+        private final SparseIntArray mConfigAbsoluteMaxSlots = new SparseIntArray(NUM_WORK_TYPES);
 
-        private int mNumRunningFgJobs;
-        private int mNumRunningBgJobs;
+        /**
+         * Numbers may be lower in this than in {@link #mConfigNumReservedSlots} if there aren't
+         * enough ready jobs of a type to take up all of the desired reserved slots.
+         */
+        private final SparseIntArray mNumActuallyReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
+        private final SparseIntArray mNumPendingJobs = new SparseIntArray(NUM_WORK_TYPES);
+        private final SparseIntArray mNumRunningJobs = new SparseIntArray(NUM_WORK_TYPES);
+        private final SparseIntArray mNumStartingJobs = new SparseIntArray(NUM_WORK_TYPES);
+        private int mNumUnspecialized = 0;
+        private int mNumUnspecializedRemaining = 0;
 
-        private int mNumPendingFgJobs;
-        private int mNumPendingBgJobs;
+        void setConfig(@NonNull WorkTypeConfig workTypeConfig) {
+            mConfigMaxTotal = workTypeConfig.getMaxTotal();
+            mConfigNumReservedSlots.put(WORK_TYPE_TOP,
+                    workTypeConfig.getMinReserved(WORK_TYPE_TOP));
+            mConfigNumReservedSlots.put(WORK_TYPE_BG, workTypeConfig.getMinReserved(WORK_TYPE_BG));
+            mConfigAbsoluteMaxSlots.put(WORK_TYPE_TOP, workTypeConfig.getMax(WORK_TYPE_TOP));
+            mConfigAbsoluteMaxSlots.put(WORK_TYPE_BG, workTypeConfig.getMax(WORK_TYPE_BG));
 
-        private int mNumStartingFgJobs;
-        private int mNumStartingBgJobs;
-
-        private int mNumReservedForBg;
-        private int mNumActualMaxFgJobs;
-        private int mNumActualMaxBgJobs;
-
-        void reset(int numTotalMaxJobs, int numMaxBgJobs, int numMinBgJobs) {
-            mConfigNumMaxTotalJobs = numTotalMaxJobs;
-            mConfigNumMaxBgJobs = numMaxBgJobs;
-            mConfigNumMinBgJobs = numMinBgJobs;
-
-            mNumRunningFgJobs = 0;
-            mNumRunningBgJobs = 0;
-
-            mNumPendingFgJobs = 0;
-            mNumPendingBgJobs = 0;
-
-            mNumStartingFgJobs = 0;
-            mNumStartingBgJobs = 0;
-
-            mNumReservedForBg = 0;
-            mNumActualMaxFgJobs = 0;
-            mNumActualMaxBgJobs = 0;
+            mNumUnspecialized = mConfigMaxTotal;
+            mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_TOP);
+            mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_BG);
+            mNumUnspecialized -= mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP);
+            mNumUnspecialized -= mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG);
+            calculateUnspecializedRemaining();
         }
 
-        void incrementRunningJobCount(boolean isFg) {
-            if (isFg) {
-                mNumRunningFgJobs++;
-            } else {
-                mNumRunningBgJobs++;
+        private void calculateUnspecializedRemaining() {
+            mNumUnspecializedRemaining = mNumUnspecialized;
+            for (int i = mNumRunningJobs.size() - 1; i >= 0; --i) {
+                mNumUnspecializedRemaining -= mNumRunningJobs.valueAt(i);
             }
         }
 
-        void incrementPendingJobCount(boolean isFg) {
-            if (isFg) {
-                mNumPendingFgJobs++;
-            } else {
-                mNumPendingBgJobs++;
+        void resetCounts() {
+            mNumActuallyReservedSlots.clear();
+            mNumPendingJobs.clear();
+            mNumRunningJobs.clear();
+            resetStagingCount();
+        }
+
+        void resetStagingCount() {
+            mNumStartingJobs.clear();
+        }
+
+        void incrementRunningJobCount(@WorkType int workType) {
+            mNumRunningJobs.put(workType, mNumRunningJobs.get(workType) + 1);
+        }
+
+        void incrementPendingJobCount(int workTypes) {
+            // We don't know which type we'll classify the job as when we run it yet, so make sure
+            // we have space in all applicable slots.
+            if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) {
+                mNumPendingJobs.put(WORK_TYPE_TOP, mNumPendingJobs.get(WORK_TYPE_TOP) + 1);
+            }
+            if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) {
+                mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + 1);
             }
         }
 
-        void onStartingNewJob(boolean isFg) {
-            if (isFg) {
-                mNumStartingFgJobs++;
+        void stageJob(@WorkType int workType) {
+            final int newNumStartingJobs = mNumStartingJobs.get(workType) + 1;
+            mNumStartingJobs.put(workType, newNumStartingJobs);
+            mNumPendingJobs.put(workType, Math.max(0, mNumPendingJobs.get(workType) - 1));
+            if (newNumStartingJobs + mNumRunningJobs.get(workType)
+                    > mNumActuallyReservedSlots.get(workType)) {
+                mNumUnspecializedRemaining--;
+            }
+        }
+
+        void onStagedJobFailed(@WorkType int workType) {
+            final int oldNumStartingJobs = mNumStartingJobs.get(workType);
+            if (oldNumStartingJobs == 0) {
+                Slog.e(TAG, "# staged jobs for " + workType + " went negative.");
+                // We are in a bad state. We will eventually recover when the pending list is
+                // regenerated.
+                return;
+            }
+            mNumStartingJobs.put(workType, oldNumStartingJobs - 1);
+            maybeAdjustReservations(workType);
+        }
+
+        private void maybeAdjustReservations(@WorkType int workType) {
+            // Always make sure we reserve the minimum number of slots in case new jobs become ready
+            // soon.
+            final int numRemainingForType = Math.max(mConfigNumReservedSlots.get(workType),
+                    mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType)
+                            + mNumPendingJobs.get(workType));
+            if (numRemainingForType < mNumActuallyReservedSlots.get(workType)) {
+                // We've run all jobs for this type. Let another type use it now.
+                mNumActuallyReservedSlots.put(workType, numRemainingForType);
+                mNumUnspecializedRemaining++;
+            }
+        }
+
+        void onJobStarted(@WorkType int workType) {
+            mNumRunningJobs.put(workType, mNumRunningJobs.get(workType) + 1);
+            final int oldNumStartingJobs = mNumStartingJobs.get(workType);
+            if (oldNumStartingJobs == 0) {
+                Slog.e(TAG, "# stated jobs for " + workType + " went negative.");
+                // We are in a bad state. We will eventually recover when the pending list is
+                // regenerated. For now, only modify the running count.
             } else {
-                mNumStartingBgJobs++;
+                mNumStartingJobs.put(workType, oldNumStartingJobs - 1);
             }
         }
 
         void onCountDone() {
-            // Note some variables are used only here but are made class members in order to have
-            // them on logcat / dumpsys.
+            // Calculate how many slots to reserve for each work type. "Unspecialized" slots will
+            // be reserved for higher importance types first (ie. top before bg).
+            mNumUnspecialized = mConfigMaxTotal;
+            final int numTop = mNumRunningJobs.get(WORK_TYPE_TOP)
+                    + mNumPendingJobs.get(WORK_TYPE_TOP);
+            final int resTop = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_TOP), numTop);
+            mNumActuallyReservedSlots.put(WORK_TYPE_TOP, resTop);
+            mNumUnspecialized -= resTop;
+            final int numBg = mNumRunningJobs.get(WORK_TYPE_BG) + mNumPendingJobs.get(WORK_TYPE_BG);
+            final int resBg = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_BG), numBg);
+            mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg);
+            mNumUnspecialized -= resBg;
+            calculateUnspecializedRemaining();
 
-            // How many slots should we allocate to BG jobs at least?
-            // That's basically "getMinBg()", but if there are less jobs, decrease it.
-            // (e.g. even if min-bg is 2, if there's only 1 running+pending job, this has to be 1.)
-            final int reservedForBg = Math.min(
-                    mConfigNumMinBgJobs,
-                    mNumRunningBgJobs + mNumPendingBgJobs);
-
-            // However, if there are FG jobs already running, we have to adjust it.
-            mNumReservedForBg = Math.min(reservedForBg,
-                    mConfigNumMaxTotalJobs - mNumRunningFgJobs);
-
-            // Max FG is [total - [number needed for BG jobs]]
-            // [number needed for BG jobs] is the bigger one of [running BG] or [reserved BG]
-            final int maxFg =
-                    mConfigNumMaxTotalJobs - Math.max(mNumRunningBgJobs, mNumReservedForBg);
-
-            // The above maxFg is the theoretical max. If there are less FG jobs, the actual
-            // max FG will be lower accordingly.
-            mNumActualMaxFgJobs = Math.min(
-                    maxFg,
-                    mNumRunningFgJobs + mNumPendingFgJobs);
-
-            // Max BG is [total - actual max FG], but cap at [config max BG].
-            final int maxBg = Math.min(
-                    mConfigNumMaxBgJobs,
-                    mConfigNumMaxTotalJobs - mNumActualMaxFgJobs);
-
-            // If there are less BG jobs than maxBg, then reduce the actual max BG accordingly.
-            // This isn't needed for the logic to work, but this will give consistent output
-            // on logcat and dumpsys.
-            mNumActualMaxBgJobs = Math.min(
-                    maxBg,
-                    mNumRunningBgJobs + mNumPendingBgJobs);
+            // Assign remaining unspecialized based on ranking.
+            int unspecializedAssigned = Math.max(0,
+                    Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP),
+                            Math.min(mNumUnspecializedRemaining, numTop - resTop)));
+            mNumActuallyReservedSlots.put(WORK_TYPE_TOP, resTop + unspecializedAssigned);
+            mNumUnspecializedRemaining -= unspecializedAssigned;
+            unspecializedAssigned = Math.max(0,
+                    Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG),
+                            Math.min(mNumUnspecializedRemaining, numBg - resBg)));
+            mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg + unspecializedAssigned);
+            mNumUnspecializedRemaining -= unspecializedAssigned;
         }
 
-        boolean canJobStart(boolean isFg) {
-            if (isFg) {
-                return mNumRunningFgJobs + mNumStartingFgJobs < mNumActualMaxFgJobs;
-            } else {
-                return mNumRunningBgJobs + mNumStartingBgJobs < mNumActualMaxBgJobs;
+        int canJobStart(int workTypes) {
+            if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) {
+                final int maxAllowed = Math.min(
+                        mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP),
+                        mNumActuallyReservedSlots.get(WORK_TYPE_TOP) + mNumUnspecializedRemaining);
+                if (mNumRunningJobs.get(WORK_TYPE_TOP) + mNumStartingJobs.get(WORK_TYPE_TOP)
+                        < maxAllowed) {
+                    return WORK_TYPE_TOP;
+                }
             }
-        }
-
-        public int getNumStartingFgJobs() {
-            return mNumStartingFgJobs;
-        }
-
-        public int getNumStartingBgJobs() {
-            return mNumStartingBgJobs;
-        }
-
-        int getTotalRunningJobCountToNote() {
-            return mNumRunningFgJobs + mNumRunningBgJobs
-                    + mNumStartingFgJobs + mNumStartingBgJobs;
-        }
-
-        int getFgRunningJobCountToNote() {
-            return mNumRunningFgJobs + mNumStartingFgJobs;
-        }
-
-        void logStatus() {
-            if (DEBUG) {
-                Slog.d(TAG, "assignJobsToContexts: " + this);
+            if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) {
+                final int maxAllowed = Math.min(
+                        mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG),
+                        mNumActuallyReservedSlots.get(WORK_TYPE_BG) + mNumUnspecializedRemaining);
+                if (mNumRunningJobs.get(WORK_TYPE_BG) + mNumStartingJobs.get(WORK_TYPE_BG)
+                        < maxAllowed) {
+                    return WORK_TYPE_BG;
+                }
             }
+            return WORK_TYPE_NONE;
+        }
+
+        int getRunningJobCount(@WorkType final int workType) {
+            return mNumRunningJobs.get(workType, 0);
         }
 
         public String toString() {
-            final int totalFg = mNumRunningFgJobs + mNumStartingFgJobs;
-            final int totalBg = mNumRunningBgJobs + mNumStartingBgJobs;
-            return String.format(
-                    "Config={tot=%d bg min/max=%d/%d}"
-                            + " Running[FG/BG (total)]: %d / %d (%d)"
-                            + " Pending: %d / %d (%d)"
-                            + " Actual max: %d%s / %d%s (%d%s)"
-                            + " Res BG: %d"
-                            + " Starting: %d / %d (%d)"
-                            + " Total: %d%s / %d%s (%d%s)",
-                    mConfigNumMaxTotalJobs, mConfigNumMinBgJobs, mConfigNumMaxBgJobs,
+            StringBuilder sb = new StringBuilder();
 
-                    mNumRunningFgJobs, mNumRunningBgJobs, mNumRunningFgJobs + mNumRunningBgJobs,
+            sb.append("Config={");
+            sb.append("tot=").append(mConfigMaxTotal);
+            sb.append(" mins=");
+            sb.append(mConfigNumReservedSlots);
+            sb.append(" maxs=");
+            sb.append(mConfigAbsoluteMaxSlots);
+            sb.append("}");
 
-                    mNumPendingFgJobs, mNumPendingBgJobs, mNumPendingFgJobs + mNumPendingBgJobs,
+            sb.append(", act res=").append(mNumActuallyReservedSlots);
+            sb.append(", Pending=").append(mNumPendingJobs);
+            sb.append(", Running=").append(mNumRunningJobs);
+            sb.append(", Staged=").append(mNumStartingJobs);
+            sb.append(", # unspecialized remaining=").append(mNumUnspecializedRemaining);
 
-                    mNumActualMaxFgJobs, (totalFg <= mConfigNumMaxTotalJobs) ? "" : "*",
-                    mNumActualMaxBgJobs, (totalBg <= mConfigNumMaxBgJobs) ? "" : "*",
-                    mNumActualMaxFgJobs + mNumActualMaxBgJobs,
-                    (mNumActualMaxFgJobs + mNumActualMaxBgJobs <= mConfigNumMaxTotalJobs)
-                            ? "" : "*",
-
-                    mNumReservedForBg,
-
-                    mNumStartingFgJobs, mNumStartingBgJobs, mNumStartingFgJobs + mNumStartingBgJobs,
-
-                    totalFg, (totalFg <= mNumActualMaxFgJobs) ? "" : "*",
-                    totalBg, (totalBg <= mNumActualMaxBgJobs) ? "" : "*",
-                    totalFg + totalBg, (totalFg + totalBg <= mConfigNumMaxTotalJobs) ? "" : "*"
-            );
-        }
-
-        public void dumpProto(ProtoOutputStream proto, long fieldId) {
-            final long token = proto.start(fieldId);
-
-            proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_TOTAL_JOBS, mConfigNumMaxTotalJobs);
-            proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_BG_JOBS, mConfigNumMaxBgJobs);
-            proto.write(JobCountTrackerProto.CONFIG_NUM_MIN_BG_JOBS, mConfigNumMinBgJobs);
-
-            proto.write(JobCountTrackerProto.NUM_RUNNING_FG_JOBS, mNumRunningFgJobs);
-            proto.write(JobCountTrackerProto.NUM_RUNNING_BG_JOBS, mNumRunningBgJobs);
-
-            proto.write(JobCountTrackerProto.NUM_PENDING_FG_JOBS, mNumPendingFgJobs);
-            proto.write(JobCountTrackerProto.NUM_PENDING_BG_JOBS, mNumPendingBgJobs);
-
-            proto.write(JobCountTrackerProto.NUM_ACTUAL_MAX_FG_JOBS, mNumActualMaxFgJobs);
-            proto.write(JobCountTrackerProto.NUM_ACTUAL_MAX_BG_JOBS, mNumActualMaxBgJobs);
-
-            proto.write(JobCountTrackerProto.NUM_RESERVED_FOR_BG, mNumReservedForBg);
-
-            proto.write(JobCountTrackerProto.NUM_STARTING_FG_JOBS, mNumStartingFgJobs);
-            proto.write(JobCountTrackerProto.NUM_STARTING_BG_JOBS, mNumStartingBgJobs);
-
-            proto.end(token);
+            return sb.toString();
         }
     }
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 885662c..ba78bda 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -380,7 +380,6 @@
                         default:
                             if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY)
                                     && !concurrencyUpdated) {
-                                mConstants.updateConcurrencyConstantsLocked();
                                 mConcurrencyManager.updateConfigLocked();
                                 concurrencyUpdated = true;
                             } else {
@@ -408,119 +407,6 @@
                 mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
     }
 
-    static class MaxJobCounts {
-        private final int mTotalDefault;
-        private final String mTotalKey;
-        private final int mMaxBgDefault;
-        private final String mMaxBgKey;
-        private final int mMinBgDefault;
-        private final String mMinBgKey;
-        private int mTotal;
-        private int mMaxBg;
-        private int mMinBg;
-
-        MaxJobCounts(int totalDefault, String totalKey,
-                int maxBgDefault, String maxBgKey, int minBgDefault, String minBgKey) {
-            mTotalKey = totalKey;
-            mTotal = mTotalDefault = totalDefault;
-            mMaxBgKey = maxBgKey;
-            mMaxBg = mMaxBgDefault = maxBgDefault;
-            mMinBgKey = minBgKey;
-            mMinBg = mMinBgDefault = minBgDefault;
-        }
-
-        public void update() {
-            mTotal = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
-                    mTotalKey, mTotalDefault);
-            mMaxBg = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
-                    mMaxBgKey, mMaxBgDefault);
-            mMinBg = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
-                    mMinBgKey, mMinBgDefault);
-
-            // Ensure total in the range [1, MAX_JOB_CONTEXTS_COUNT].
-            mTotal = Math.min(Math.max(1, mTotal), MAX_JOB_CONTEXTS_COUNT);
-
-            // Ensure maxBg in the range [1, total].
-            mMaxBg = Math.min(Math.max(1, mMaxBg), mTotal);
-
-            // Ensure minBg in the range [0, min(maxBg, total - 1)]
-            mMinBg = Math.min(Math.max(0, mMinBg), Math.min(mMaxBg, mTotal - 1));
-        }
-
-        /** Total number of jobs to run simultaneously. */
-        public int getMaxTotal() {
-            return mTotal;
-        }
-
-        /** Max number of BG (== owned by non-TOP apps) jobs to run simultaneously. */
-        public int getMaxBg() {
-            return mMaxBg;
-        }
-
-        /**
-         * We try to run at least this many BG (== owned by non-TOP apps) jobs, when there are any
-         * pending, rather than always running the TOTAL number of FG jobs.
-         */
-        public int getMinBg() {
-            return mMinBg;
-        }
-
-        public void dump(PrintWriter pw, String prefix) {
-            pw.print(prefix);
-            pw.print(mTotalKey);
-            pw.print("=");
-            pw.print(mTotal);
-            pw.println();
-
-            pw.print(prefix);
-            pw.print(mMaxBgKey);
-            pw.print("=");
-            pw.print(mMaxBg);
-            pw.println();
-
-            pw.print(prefix);
-            pw.print(mMinBgKey);
-            pw.print("=");
-            pw.print(mMinBg);
-            pw.println();
-        }
-
-        public void dumpProto(ProtoOutputStream proto, long fieldId) {
-            final long token = proto.start(fieldId);
-            proto.write(MaxJobCountsProto.TOTAL_JOBS, mTotal);
-            proto.write(MaxJobCountsProto.MAX_BG, mMaxBg);
-            proto.write(MaxJobCountsProto.MIN_BG, mMinBg);
-            proto.end(token);
-        }
-    }
-
-    /** {@link MaxJobCounts} for each memory trim level. */
-    static class MaxJobCountsPerMemoryTrimLevel {
-        public final MaxJobCounts normal;
-        public final MaxJobCounts moderate;
-        public final MaxJobCounts low;
-        public final MaxJobCounts critical;
-
-        MaxJobCountsPerMemoryTrimLevel(
-                MaxJobCounts normal,
-                MaxJobCounts moderate, MaxJobCounts low,
-                MaxJobCounts critical) {
-            this.normal = normal;
-            this.moderate = moderate;
-            this.low = low;
-            this.critical = critical;
-        }
-
-        public void dumpProto(ProtoOutputStream proto, long fieldId) {
-            final long token = proto.start(fieldId);
-            normal.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.NORMAL);
-            moderate.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.MODERATE);
-            low.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.LOW);
-            critical.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.CRITICAL);
-            proto.end(token);
-        }
-    }
-
     /**
      * All times are in milliseconds. Any access to this class or its fields should be done while
      * holding the JobSchedulerService.mLock lock.
@@ -580,49 +466,6 @@
          */
         float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR;
 
-        /** Prefix for all of the max_job constants. */
-        private static final String KEY_PREFIX_MAX_JOB =
-                JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY + "max_job_";
-
-        // Max job counts for screen on / off, for each memory trim level.
-        final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_ON =
-                new MaxJobCountsPerMemoryTrimLevel(
-                        new MaxJobCounts(
-                                8, KEY_PREFIX_MAX_JOB + "total_on_normal",
-                                6, KEY_PREFIX_MAX_JOB + "max_bg_on_normal",
-                                2, KEY_PREFIX_MAX_JOB + "min_bg_on_normal"),
-                        new MaxJobCounts(
-                                8, KEY_PREFIX_MAX_JOB + "total_on_moderate",
-                                4, KEY_PREFIX_MAX_JOB + "max_bg_on_moderate",
-                                2, KEY_PREFIX_MAX_JOB + "min_bg_on_moderate"),
-                        new MaxJobCounts(
-                                5, KEY_PREFIX_MAX_JOB + "total_on_low",
-                                1, KEY_PREFIX_MAX_JOB + "max_bg_on_low",
-                                1, KEY_PREFIX_MAX_JOB + "min_bg_on_low"),
-                        new MaxJobCounts(
-                                5, KEY_PREFIX_MAX_JOB + "total_on_critical",
-                                1, KEY_PREFIX_MAX_JOB + "max_bg_on_critical",
-                                1, KEY_PREFIX_MAX_JOB + "min_bg_on_critical"));
-
-        final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_OFF =
-                new MaxJobCountsPerMemoryTrimLevel(
-                        new MaxJobCounts(
-                                10, KEY_PREFIX_MAX_JOB + "total_off_normal",
-                                6, KEY_PREFIX_MAX_JOB + "max_bg_off_normal",
-                                2, KEY_PREFIX_MAX_JOB + "min_bg_off_normal"),
-                        new MaxJobCounts(
-                                10, KEY_PREFIX_MAX_JOB + "total_off_moderate",
-                                4, KEY_PREFIX_MAX_JOB + "max_bg_off_moderate",
-                                2, KEY_PREFIX_MAX_JOB + "min_bg_off_moderate"),
-                        new MaxJobCounts(
-                                5, KEY_PREFIX_MAX_JOB + "total_off_low",
-                                1, KEY_PREFIX_MAX_JOB + "max_bg_off_low",
-                                1, KEY_PREFIX_MAX_JOB + "min_bg_off_low"),
-                        new MaxJobCounts(
-                                5, KEY_PREFIX_MAX_JOB + "total_off_critical",
-                                1, KEY_PREFIX_MAX_JOB + "max_bg_off_critical",
-                                1, KEY_PREFIX_MAX_JOB + "min_bg_off_critical"));
-
         /**
          * The minimum backoff time to allow for linear backoff.
          */
@@ -686,18 +529,6 @@
                     DEFAULT_MODERATE_USE_FACTOR);
         }
 
-        void updateConcurrencyConstantsLocked() {
-            MAX_JOB_COUNTS_SCREEN_ON.normal.update();
-            MAX_JOB_COUNTS_SCREEN_ON.moderate.update();
-            MAX_JOB_COUNTS_SCREEN_ON.low.update();
-            MAX_JOB_COUNTS_SCREEN_ON.critical.update();
-
-            MAX_JOB_COUNTS_SCREEN_OFF.normal.update();
-            MAX_JOB_COUNTS_SCREEN_OFF.moderate.update();
-            MAX_JOB_COUNTS_SCREEN_OFF.low.update();
-            MAX_JOB_COUNTS_SCREEN_OFF.critical.update();
-        }
-
         private void updateBackoffConstantsLocked() {
             MIN_LINEAR_BACKOFF_TIME_MS = DeviceConfig.getLong(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                     KEY_MIN_LINEAR_BACKOFF_TIME_MS,
@@ -747,16 +578,6 @@
             pw.print(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println();
             pw.print(KEY_MODERATE_USE_FACTOR, MODERATE_USE_FACTOR).println();
 
-            MAX_JOB_COUNTS_SCREEN_ON.normal.dump(pw, "");
-            MAX_JOB_COUNTS_SCREEN_ON.moderate.dump(pw, "");
-            MAX_JOB_COUNTS_SCREEN_ON.low.dump(pw, "");
-            MAX_JOB_COUNTS_SCREEN_ON.critical.dump(pw, "");
-
-            MAX_JOB_COUNTS_SCREEN_OFF.normal.dump(pw, "");
-            MAX_JOB_COUNTS_SCREEN_OFF.moderate.dump(pw, "");
-            MAX_JOB_COUNTS_SCREEN_OFF.low.dump(pw, "");
-            MAX_JOB_COUNTS_SCREEN_OFF.critical.dump(pw, "");
-
             pw.print(KEY_MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS).println();
             pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
             pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
@@ -781,9 +602,6 @@
             proto.write(ConstantsProto.HEAVY_USE_FACTOR, HEAVY_USE_FACTOR);
             proto.write(ConstantsProto.MODERATE_USE_FACTOR, MODERATE_USE_FACTOR);
 
-            MAX_JOB_COUNTS_SCREEN_ON.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_ON);
-            MAX_JOB_COUNTS_SCREEN_OFF.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_OFF);
-
             proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS);
             proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS);
             proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 26b5abe..247b421 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -16,6 +16,7 @@
 
 package com.android.server.job;
 
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
@@ -124,9 +125,12 @@
      *
      * Any reads (dereferences) not done from the handler thread must be synchronized on
      * {@link #mLock}.
-     * Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}.
+     * Writes can only be done from the handler thread,
+     * or {@link #executeRunnableJob(JobStatus, int)}.
      */
     private JobStatus mRunningJob;
+    @JobConcurrencyManager.WorkType
+    private int mRunningJobWorkType;
     private JobCallback mRunningCallback;
     /** Used to store next job to run when current job is to be preempted. */
     private int mPreferredUid;
@@ -181,30 +185,26 @@
 
     JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
             JobPackageTracker tracker, Looper looper) {
-        this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper);
-    }
-
-    @VisibleForTesting
-    JobServiceContext(Context context, Object lock, IBatteryStats batteryStats,
-            JobPackageTracker tracker, JobCompletedListener completedListener, Looper looper) {
-        mContext = context;
-        mLock = lock;
+        mContext = service.getContext();
+        mLock = service.getLock();
         mBatteryStats = batteryStats;
         mJobPackageTracker = tracker;
         mCallbackHandler = new JobServiceHandler(looper);
-        mCompletedListener = completedListener;
+        mCompletedListener = service;
         mAvailable = true;
         mVerb = VERB_FINISHED;
         mPreferredUid = NO_PREFERRED_UID;
     }
 
     /**
-     * Give a job to this context for execution. Callers must first check {@link #getRunningJobLocked()}
+     * Give a job to this context for execution. Callers must first check {@link
+     * #getRunningJobLocked()}
      * and ensure it is null to make sure this is a valid context.
+     *
      * @param job The status of the job that we are going to run.
      * @return True if the job is valid and is running. False if the job cannot be executed.
      */
-    boolean executeRunnableJob(JobStatus job) {
+    boolean executeRunnableJob(JobStatus job, @JobConcurrencyManager.WorkType int workType) {
         synchronized (mLock) {
             if (!mAvailable) {
                 Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
@@ -214,6 +214,7 @@
             mPreferredUid = NO_PREFERRED_UID;
 
             mRunningJob = job;
+            mRunningJobWorkType = workType;
             mRunningCallback = new JobCallback();
             final boolean isDeadlineExpired =
                     job.hasDeadlineConstraint() &&
@@ -282,6 +283,7 @@
                     Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
                 }
                 mRunningJob = null;
+                mRunningJobWorkType = WORK_TYPE_NONE;
                 mRunningCallback = null;
                 mParams = null;
                 mExecutionStartTimeElapsed = 0L;
@@ -326,6 +328,11 @@
         return mRunningJob;
     }
 
+    @JobConcurrencyManager.WorkType
+    int getRunningJobWorkType() {
+        return mRunningJobWorkType;
+    }
+
     /**
      * Used only for debugging. Will return <code>"&lt;null&gt;"</code> if there is no job running.
      */
@@ -831,6 +838,7 @@
         mContext.unbindService(JobServiceContext.this);
         mWakeLock = null;
         mRunningJob = null;
+        mRunningJobWorkType = WORK_TYPE_NONE;
         mRunningCallback = null;
         mParams = null;
         mVerb = VERB_FINISHED;
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 09dc7d2..539c3c9 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
@@ -305,6 +305,7 @@
     public Network network;
     public ServiceInfo serviceInfo;
 
+    /** The evaluated priority of the job when it started running. */
     public int lastEvaluatedPriority;
 
     // If non-null, this is work that has been enqueued for the job.
diff --git a/services/tests/servicestests/src/com/android/server/job/JobCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
similarity index 79%
rename from services/tests/servicestests/src/com/android/server/job/JobCountTrackerTest.java
rename to services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
index e5529cb..15a9bcf 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobCountTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
@@ -16,36 +16,42 @@
 
 package com.android.server.job;
 
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.util.Log;
+import android.util.Pair;
 
-import com.android.server.job.JobConcurrencyManager.JobCountTracker;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.job.JobConcurrencyManager.WorkCountTracker;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
 import java.util.Random;
 
-import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
-
 /**
- * Test for {@link com.android.server.job.JobConcurrencyManager.JobCountTracker}.
+ * Test for {@link WorkCountTracker}.
  */
 @RunWith(AndroidJUnit4.class)
 @MediumTest
-public class JobCountTrackerTest {
-    private static final String TAG = "JobCountTrackerTest";
+public class WorkCountTrackerTest {
+    private static final String TAG = "WorkerCountTrackerTest";
 
     private Random mRandom;
-    private JobCountTracker mJobCountTracker;
+    private WorkCountTracker mWorkCountTracker;
 
     @Before
     public void setUp() {
         mRandom = new Random(1); // Always use the same series of pseudo random values.
-        mJobCountTracker = new JobCountTracker();
+        mWorkCountTracker = new WorkCountTracker();
     }
 
     /**
@@ -83,44 +89,57 @@
 
 
     private void startPendingJobs(Jobs jobs, int totalMax, int maxBg, int minBg) {
-        mJobCountTracker.reset(totalMax, maxBg, minBg);
+        mWorkCountTracker.setConfig(new JobConcurrencyManager.WorkTypeConfig("critical",
+                totalMax,
+                // defaultMin
+                List.of(Pair.create(WORK_TYPE_TOP, totalMax - maxBg),
+                        Pair.create(WORK_TYPE_BG, minBg)),
+                // defaultMax
+                List.of(Pair.create(WORK_TYPE_BG, maxBg))));
+        mWorkCountTracker.resetCounts();
 
         for (int i = 0; i < jobs.runningFg; i++) {
-            mJobCountTracker.incrementRunningJobCount(true);
+            mWorkCountTracker.incrementRunningJobCount(WORK_TYPE_TOP);
         }
         for (int i = 0; i < jobs.runningBg; i++) {
-            mJobCountTracker.incrementRunningJobCount(false);
+            mWorkCountTracker.incrementRunningJobCount(WORK_TYPE_BG);
         }
 
         for (int i = 0; i < jobs.pendingFg; i++) {
-            mJobCountTracker.incrementPendingJobCount(true);
+            mWorkCountTracker.incrementPendingJobCount(WORK_TYPE_TOP);
         }
         for (int i = 0; i < jobs.pendingBg; i++) {
-            mJobCountTracker.incrementPendingJobCount(false);
+            mWorkCountTracker.incrementPendingJobCount(WORK_TYPE_BG);
         }
 
-        mJobCountTracker.onCountDone();
+        mWorkCountTracker.onCountDone();
 
-        while ((jobs.pendingFg > 0 && mJobCountTracker.canJobStart(true))
-                || (jobs.pendingBg > 0 && mJobCountTracker.canJobStart(false))) {
+        while ((jobs.pendingFg > 0
+                && mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE)
+                || (jobs.pendingBg > 0
+                && mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE)) {
             final boolean isStartingFg = mRandom.nextBoolean();
 
             if (isStartingFg) {
-                if (jobs.pendingFg > 0 && mJobCountTracker.canJobStart(true)) {
+                if (jobs.pendingFg > 0
+                        && mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE) {
                     jobs.pendingFg--;
                     jobs.runningFg++;
-                    mJobCountTracker.onStartingNewJob(true);
+                    mWorkCountTracker.stageJob(WORK_TYPE_TOP);
+                    mWorkCountTracker.onJobStarted(WORK_TYPE_TOP);
                 }
             } else {
-                if (jobs.pendingBg > 0 && mJobCountTracker.canJobStart(false)) {
+                if (jobs.pendingBg > 0
+                        && mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE) {
                     jobs.pendingBg--;
                     jobs.runningBg++;
-                    mJobCountTracker.onStartingNewJob(false);
+                    mWorkCountTracker.stageJob(WORK_TYPE_BG);
+                    mWorkCountTracker.onJobStarted(WORK_TYPE_BG);
                 }
             }
         }
 
-        Log.i(TAG, "" + mJobCountTracker);
+        Log.i(TAG, "" + mWorkCountTracker);
     }
 
     /**
@@ -277,6 +296,7 @@
 
         startPendingJobs(jobs, totalMax, maxBg, minBg);
 
+//        fail(mWorkerCountTracker.toString());
         assertThat(jobs.runningFg).isEqualTo(resultRunningFg);
         assertThat(jobs.runningBg).isEqualTo(resultRunningBg);
 
@@ -300,6 +320,8 @@
         checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 1, /*res run/pen=*/ 5, 1, 5, 0);
         checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 4, 2, 6, 1);
 
+        checkSimple(8, 6, 2, /*run=*/ 0, 0, /*pen=*/ 0, 49, /*res run/pen=*/ 0, 6, 0, 43);
+
         checkSimple(6, 4, 2, /*run=*/ 6, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 6, 0, 10, 3);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
similarity index 63%
rename from services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java
rename to services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
index 4c36747..fba36cb 100644
--- a/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
@@ -15,22 +15,34 @@
  */
 package com.android.server.job;
 
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
+
 import android.annotation.Nullable;
 import android.provider.DeviceConfig;
+import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.server.job.JobSchedulerService.MaxJobCounts;
+import com.android.server.job.JobConcurrencyManager.WorkTypeConfig;
 
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-public class MaxJobCountsTest {
+public class WorkTypeConfigTest {
+    private static final String KEY_MAX_TOTAL = "concurrency_max_total_test";
+    private static final String KEY_MAX_TOP = "concurrency_max_top_test";
+    private static final String KEY_MAX_BG = "concurrency_max_bg_test";
+    private static final String KEY_MIN_TOP = "concurrency_min_top_test";
+    private static final String KEY_MIN_BG = "concurrency_min_bg_test";
+
     @After
     public void tearDown() throws Exception {
         resetConfig();
@@ -38,9 +50,11 @@
 
     private void resetConfig() {
         // DeviceConfig.resetToDefaults() doesn't work here. Need to reset constants manually.
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, "total", "", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, "maxbg", "", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, "minbg", "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOTAL, "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOP, "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BG, "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_TOP, "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BG, "", false);
     }
 
     private void check(@Nullable DeviceConfig.Properties config,
@@ -51,16 +65,19 @@
             DeviceConfig.setProperties(config);
         }
 
-        final MaxJobCounts counts = new JobSchedulerService.MaxJobCounts(
-                defaultTotal, "total",
-                defaultMaxBg, "maxbg",
-                defaultMinBg, "minbg");
+        final WorkTypeConfig counts = new WorkTypeConfig("test",
+                defaultTotal,
+                // defaultMin
+                List.of(Pair.create(WORK_TYPE_TOP, defaultTotal - defaultMaxBg),
+                        Pair.create(WORK_TYPE_BG, defaultMinBg)),
+                // defaultMax
+                List.of(Pair.create(WORK_TYPE_BG, defaultMaxBg)));
 
-        counts.update();
+        counts.update(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER));
 
         Assert.assertEquals(expectedTotal, counts.getMaxTotal());
-        Assert.assertEquals(expectedMaxBg, counts.getMaxBg());
-        Assert.assertEquals(expectedMinBg, counts.getMinBg());
+        Assert.assertEquals(expectedMaxBg, counts.getMax(WORK_TYPE_BG));
+        Assert.assertEquals(expectedMinBg, counts.getMinReserved(WORK_TYPE_BG));
     }
 
     @Test
@@ -80,19 +97,19 @@
 
         // Test for overriding with a setting string.
         check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
-                        .setInt("total", 5)
-                        .setInt("maxbg", 4)
-                        .setInt("minbg", 3)
+                        .setInt(KEY_MAX_TOTAL, 5)
+                        .setInt(KEY_MAX_BG, 4)
+                        .setInt(KEY_MIN_BG, 3)
                         .build(),
                 /*default*/ 9, 9, 9, /*expected*/ 5, 4, 3);
         check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
-                        .setInt("total", 5).build(),
+                        .setInt(KEY_MAX_TOTAL, 5).build(),
                 /*default*/ 9, 9, 9, /*expected*/ 5, 5, 4);
         check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
-                        .setInt("maxbg", 4).build(),
+                        .setInt(KEY_MAX_BG, 4).build(),
                 /*default*/ 9, 9, 9, /*expected*/ 9, 4, 4);
         check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
-                        .setInt("minbg", 3).build(),
+                        .setInt(KEY_MIN_BG, 3).build(),
                 /*default*/ 9, 9, 9, /*expected*/ 9, 9, 3);
     }
 }