Merge "Shift some processing to a handler." into udc-dev
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 bad4060..70b06cb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -233,7 +233,7 @@
         }
     }
 
-    @VisibleForTesting
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public static Clock sUptimeMillisClock = new MySimpleClock(ZoneOffset.UTC) {
         @Override
         public long millis() {
@@ -241,7 +241,6 @@
         }
     };
 
-    @VisibleForTesting
     public static Clock sElapsedRealtimeClock = new MySimpleClock(ZoneOffset.UTC) {
         @Override
         public long millis() {
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 234a93c..5d2c926 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
@@ -33,7 +33,9 @@
 import android.app.job.JobInfo;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.util.ArraySet;
@@ -66,6 +68,11 @@
             | CONSTRAINT_CHARGING
             | CONSTRAINT_IDLE;
 
+    /** List of flexible constraints a job can opt into. */
+    static final int OPTIONAL_FLEXIBLE_CONSTRAINTS = CONSTRAINT_BATTERY_NOT_LOW
+            | CONSTRAINT_CHARGING
+            | CONSTRAINT_IDLE;
+
     /** List of all job flexible constraints whose satisfaction is job specific. */
     private static final int JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS = CONSTRAINT_CONNECTIVITY;
 
@@ -76,6 +83,9 @@
     private static final int NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS =
             Integer.bitCount(JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS);
 
+    static final int NUM_OPTIONAL_FLEXIBLE_CONSTRAINTS =
+            Integer.bitCount(OPTIONAL_FLEXIBLE_CONSTRAINTS);
+
     static final int NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS =
             Integer.bitCount(SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS);
 
@@ -130,6 +140,7 @@
     final FlexibilityAlarmQueue mFlexibilityAlarmQueue;
     @VisibleForTesting
     final FcConfig mFcConfig;
+    private final FcHandler mHandler;
     @VisibleForTesting
     final PrefetchController mPrefetchController;
 
@@ -174,9 +185,12 @@
                 }
             };
 
+    private static final int MSG_UPDATE_JOBS = 0;
+
     public FlexibilityController(
             JobSchedulerService service, PrefetchController prefetchController) {
         super(service);
+        mHandler = new FcHandler(AppSchedulingModuleThread.get().getLooper());
         mDeviceSupportsFlexConstraints = !mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_AUTOMOTIVE);
         mFlexibilityEnabled &= mDeviceSupportsFlexConstraints;
@@ -238,15 +252,16 @@
     boolean isFlexibilitySatisfiedLocked(JobStatus js) {
         return !mFlexibilityEnabled
                 || mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP
-                || mService.isCurrentlyRunningLocked(js)
                 || getNumSatisfiedFlexibleConstraintsLocked(js)
-                >= js.getNumRequiredFlexibleConstraints();
+                        >= js.getNumRequiredFlexibleConstraints()
+                || mService.isCurrentlyRunningLocked(js);
     }
 
     @VisibleForTesting
     @GuardedBy("mLock")
     int getNumSatisfiedFlexibleConstraintsLocked(JobStatus js) {
         return Integer.bitCount(mSatisfiedFlexibleConstraints & js.getPreferredConstraintFlags())
+                // Connectivity is job-specific, so must be handled separately.
                 + (js.getHasAccessToUnmetered() ? 1 : 0);
     }
 
@@ -267,33 +282,11 @@
                        + " constraint: " + constraint + " state: " + state);
             }
 
-            final int prevSatisfied = Integer.bitCount(mSatisfiedFlexibleConstraints);
             mSatisfiedFlexibleConstraints =
                     (mSatisfiedFlexibleConstraints & ~constraint) | (state ? constraint : 0);
-            final int curSatisfied = Integer.bitCount(mSatisfiedFlexibleConstraints);
-
-            // Only the max of the number of required flexible constraints will need to be updated
-            // The rest did not have a change in state and are still satisfied or unsatisfied.
-            final int numConstraintsToUpdate = Math.max(curSatisfied, prevSatisfied);
-
-            // In order to get the range of all potentially satisfied jobs, we start at the number
-            // of satisfied system-wide constraints and iterate to the max number of potentially
-            // satisfied constraints, determined by how many job-specific constraints exist.
-            for (int j = 0; j <= NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS; j++) {
-                final ArraySet<JobStatus> jobsByNumConstraints = mFlexibilityTracker
-                        .getJobsByNumRequiredConstraints(numConstraintsToUpdate + j);
-
-                if (jobsByNumConstraints == null) {
-                    // If there are no more jobs to iterate through we can just return.
-                    return;
-                }
-
-                for (int i = 0; i < jobsByNumConstraints.size(); i++) {
-                    JobStatus js = jobsByNumConstraints.valueAt(i);
-                    js.setFlexibilityConstraintSatisfied(
-                            nowElapsed, isFlexibilitySatisfiedLocked(js));
-                }
-            }
+            // Push the job update to the handler to avoid blocking other controllers and
+            // potentially batch back-to-back controller state updates together.
+            mHandler.obtainMessage(MSG_UPDATE_JOBS).sendToTarget();
         }
     }
 
@@ -630,6 +623,44 @@
         }
     }
 
+    private class FcHandler extends Handler {
+        FcHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_UPDATE_JOBS:
+                    removeMessages(MSG_UPDATE_JOBS);
+
+                    synchronized (mLock) {
+                        final long nowElapsed = sElapsedRealtimeClock.millis();
+                        final ArraySet<JobStatus> changedJobs = new ArraySet<>();
+
+                        for (int o = 0; o <= NUM_OPTIONAL_FLEXIBLE_CONSTRAINTS; ++o) {
+                            final ArraySet<JobStatus> jobsByNumConstraints = mFlexibilityTracker
+                                    .getJobsByNumRequiredConstraints(o);
+
+                            if (jobsByNumConstraints != null) {
+                                for (int i = 0; i < jobsByNumConstraints.size(); i++) {
+                                    final JobStatus js = jobsByNumConstraints.valueAt(i);
+                                    if (js.setFlexibilityConstraintSatisfied(
+                                            nowElapsed, isFlexibilitySatisfiedLocked(js))) {
+                                        changedJobs.add(js);
+                                    }
+                                }
+                            }
+                        }
+                        if (changedJobs.size() > 0) {
+                            mStateChangedListener.onControllerStateChanged(changedJobs);
+                        }
+                    }
+                    break;
+            }
+        }
+    }
+
     @VisibleForTesting
     class FcConfig {
         private boolean mShouldReevaluateConstraints = false;