JobScheduler: Move the concurrency calculation to a separate class

Bug: 111360323

Test: atest CtsJobSchedulerTestCases

Summary
-------
CtsJobSchedulerTestCases: Passed: 28, Failed: 5

5 tests failed
--------------
android.jobscheduler.cts.ConnectivityConstraintTest#testConnectivityConstraintExecutes_metered
android.jobscheduler.cts.ConnectivityConstraintTest#testConnectivityConstraintExecutes_withMobile
android.jobscheduler.cts.ConnectivityConstraintTest#testConnectivityConstraintExecutes_withWifi
android.jobscheduler.cts.ConnectivityConstraintTest#testUnmeteredConstraintExecutes_withWifi
android.jobscheduler.cts.ConnectivityConstraintTest#testUnmeteredConstraintFails_withMobile

 * Connectivity test failures are expected.

Change-Id: I0cf7553dc846b310887da6ae48abd5c19d25b4b4
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index ea295de..173f074 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -77,7 +77,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
-import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
@@ -111,7 +110,6 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -135,7 +133,7 @@
     public static final boolean DEBUG_STANDBY = DEBUG || false;
 
     /** The maximum number of concurrent jobs we run at one time. */
-    private static final int MAX_JOB_CONTEXTS_COUNT = 16;
+    static final int MAX_JOB_CONTEXTS_COUNT = 16;
     /** Enforce a per-app limit on scheduled jobs? */
     private static final boolean ENFORCE_MAX_JOBS = true;
     /** The maximum number of jobs that we allow an unprivileged app to schedule */
@@ -156,6 +154,7 @@
     final StandbyTracker mStandbyTracker;
     /** Tracking amount of time each package runs for. */
     final JobPackageTracker mJobPackageTracker = new JobPackageTracker();
+    final JobConcurrencyManager mConcurrencyManager;
 
     static final int MSG_JOB_EXPIRED = 0;
     static final int MSG_CHECK_JOB = 1;
@@ -173,7 +172,7 @@
     final List<JobServiceContext> mActiveServices = new ArrayList<>();
 
     /** List of controllers that will notify this service of updates to jobs. */
-    private final List<StateController> mControllers;
+    final List<StateController> mControllers;
     /** Need direct access to this for testing. */
     private final BatteryController mBatteryController;
     /** Need direct access to this for testing. */
@@ -268,22 +267,6 @@
 
     // -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
 
-    /**
-     * This array essentially stores the state of mActiveServices array.
-     * The ith index stores the job present on the ith JobServiceContext.
-     * We manipulate this array until we arrive at what jobs should be running on
-     * what JobServiceContext.
-     */
-    JobStatus[] mTmpAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
-    /**
-     * Indicates whether we need to act on this jobContext id
-     */
-    boolean[] mTmpAssignAct = new boolean[MAX_JOB_CONTEXTS_COUNT];
-    /**
-     * The uid whose jobs we would like to assign to a context.
-     */
-    int[] mTmpAssignPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
-
     private class ConstantsObserver extends ContentObserver {
         private ContentResolver mResolver;
 
@@ -1267,6 +1250,8 @@
         mConstantsObserver = new ConstantsObserver(mHandler);
         mJobSchedulerStub = new JobSchedulerStub();
 
+        mConcurrencyManager = new JobConcurrencyManager(this);
+
         // Set up the app standby bucketing tracker
         mStandbyTracker = new StandbyTracker();
         mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
@@ -2193,7 +2178,7 @@
         if (DEBUG) {
             Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
         }
-        assignJobsToContextsLocked();
+        mConcurrencyManager.assignJobsToContextsLocked();
         reportActiveLocked();
     }
 
@@ -2209,7 +2194,7 @@
         return curPriority;
     }
 
-    private int evaluateJobPriorityLocked(JobStatus job) {
+    int evaluateJobPriorityLocked(JobStatus job) {
         int priority = job.getPriority();
         if (priority >= JobInfo.PRIORITY_FOREGROUND_APP) {
             return adjustJobPriority(priority, job);
@@ -2221,161 +2206,6 @@
         return adjustJobPriority(priority, job);
     }
 
-    /**
-     * Takes jobs from pending queue and runs them on available contexts.
-     * If no contexts are available, preempts lower priority jobs to
-     * run higher priority ones.
-     * Lock on mJobs before calling this function.
-     */
-    private void assignJobsToContextsLocked() {
-        if (DEBUG) {
-            Slog.d(TAG, printPendingQueue());
-        }
-
-        int memLevel;
-        try {
-            memLevel = ActivityManager.getService().getMemoryTrimLevel();
-        } catch (RemoteException e) {
-            memLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
-        }
-        switch (memLevel) {
-            case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
-                mMaxActiveJobs = mConstants.BG_MODERATE_JOB_COUNT;
-                break;
-            case ProcessStats.ADJ_MEM_FACTOR_LOW:
-                mMaxActiveJobs = mConstants.BG_LOW_JOB_COUNT;
-                break;
-            case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
-                mMaxActiveJobs = mConstants.BG_CRITICAL_JOB_COUNT;
-                break;
-            default:
-                mMaxActiveJobs = mConstants.BG_NORMAL_JOB_COUNT;
-                break;
-        }
-
-        JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap;
-        boolean[] act = mTmpAssignAct;
-        int[] preferredUidForContext = mTmpAssignPreferredUidForContext;
-        int numActive = 0;
-        int numForeground = 0;
-        for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
-            final JobServiceContext js = mActiveServices.get(i);
-            final JobStatus status = js.getRunningJobLocked();
-            if ((contextIdToJobMap[i] = status) != null) {
-                numActive++;
-                if (status.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
-                    numForeground++;
-                }
-            }
-            act[i] = false;
-            preferredUidForContext[i] = js.getPreferredUid();
-        }
-        if (DEBUG) {
-            Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
-        }
-        for (int i=0; i<mPendingJobs.size(); i++) {
-            JobStatus nextPending = mPendingJobs.get(i);
-
-            // If job is already running, go to next job.
-            int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
-            if (jobRunningContext != -1) {
-                continue;
-            }
-
-            final int priority = evaluateJobPriorityLocked(nextPending);
-            nextPending.lastEvaluatedPriority = priority;
-
-            // Find a context for nextPending. The context should be available OR
-            // it should have lowest priority among all running jobs
-            // (sharing the same Uid as nextPending)
-            int minPriority = Integer.MAX_VALUE;
-            int minPriorityContextId = -1;
-            for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
-                JobStatus job = contextIdToJobMap[j];
-                int preferredUid = preferredUidForContext[j];
-                if (job == null) {
-                    if ((numActive < mMaxActiveJobs ||
-                            (priority >= JobInfo.PRIORITY_TOP_APP &&
-                                    numForeground < mConstants.FG_JOB_COUNT)) &&
-                            (preferredUid == nextPending.getUid() ||
-                                    preferredUid == JobServiceContext.NO_PREFERRED_UID)) {
-                        // This slot is free, and we haven't yet hit the limit on
-                        // concurrent jobs...  we can just throw the job in to here.
-                        minPriorityContextId = j;
-                        break;
-                    }
-                    // No job on this context, but nextPending can't run here because
-                    // the context has a preferred Uid or we have reached the limit on
-                    // concurrent jobs.
-                    continue;
-                }
-                if (job.getUid() != nextPending.getUid()) {
-                    continue;
-                }
-                if (evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) {
-                    continue;
-                }
-                if (minPriority > nextPending.lastEvaluatedPriority) {
-                    minPriority = nextPending.lastEvaluatedPriority;
-                    minPriorityContextId = j;
-                }
-            }
-            if (minPriorityContextId != -1) {
-                contextIdToJobMap[minPriorityContextId] = nextPending;
-                act[minPriorityContextId] = true;
-                numActive++;
-                if (priority >= JobInfo.PRIORITY_TOP_APP) {
-                    numForeground++;
-                }
-            }
-        }
-        if (DEBUG) {
-            Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
-        }
-        mJobPackageTracker.noteConcurrency(numActive, numForeground);
-        for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
-            boolean preservePreferredUid = false;
-            if (act[i]) {
-                JobStatus js = mActiveServices.get(i).getRunningJobLocked();
-                if (js != null) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJobLocked());
-                    }
-                    // preferredUid will be set to uid of currently running job.
-                    mActiveServices.get(i).preemptExecutingJobLocked();
-                    preservePreferredUid = true;
-                } else {
-                    final JobStatus pendingJob = contextIdToJobMap[i];
-                    if (DEBUG) {
-                        Slog.d(TAG, "About to run job on context "
-                                + String.valueOf(i) + ", job: " + pendingJob);
-                    }
-                    for (int ic=0; ic<mControllers.size(); ic++) {
-                        mControllers.get(ic).prepareForExecutionLocked(pendingJob);
-                    }
-                    if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) {
-                        Slog.d(TAG, "Error executing " + pendingJob);
-                    }
-                    if (mPendingJobs.remove(pendingJob)) {
-                        mJobPackageTracker.noteNonpending(pendingJob);
-                    }
-                }
-            }
-            if (!preservePreferredUid) {
-                mActiveServices.get(i).clearPreferredUid();
-            }
-        }
-    }
-
-    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;
-            }
-        }
-        return -1;
-    }
-
     final class LocalService implements JobSchedulerInternal {
 
         /**
@@ -3150,31 +2980,6 @@
         getContext().sendBroadcastAsUser(dockIntent, UserHandle.ALL);
     }
 
-    private String printContextIdToJobMap(JobStatus[] map, String initial) {
-        StringBuilder s = new StringBuilder(initial + ": ");
-        for (int i=0; i<map.length; i++) {
-            s.append("(")
-                    .append(map[i] == null? -1: map[i].getJobId())
-                    .append(map[i] == null? -1: map[i].getUid())
-                    .append(")" );
-        }
-        return s.toString();
-    }
-
-    private String printPendingQueue() {
-        StringBuilder s = new StringBuilder("Pending queue: ");
-        Iterator<JobStatus> it = mPendingJobs.iterator();
-        while (it.hasNext()) {
-            JobStatus js = it.next();
-            s.append("(")
-                    .append(js.getJob().getId())
-                    .append(", ")
-                    .append(js.getUid())
-                    .append(") ");
-        }
-        return s.toString();
-    }
-
     static void dumpHelp(PrintWriter pw) {
         pw.println("Job Scheduler (jobscheduler) dump options:");
         pw.println("  [-h] [package] ...");