OSDN Git Service

Move device idle logic into a job StateController
authorAmith Yamasani <yamasani@google.com>
Tue, 15 Mar 2016 00:15:20 +0000 (17:15 -0700)
committerAmith Yamasani <yamasani@google.com>
Tue, 22 Mar 2016 20:44:10 +0000 (13:44 -0700)
Move most of device idle (doze) logic from JobSchedulerService
to DeviceIdleJobsController.

Allow whitelisted apps to run during device idle by adding
a new constraint DEVICE_NOT_DOZING. Using the word DOZE to
disambiguate from IDLE because the latter is used to mean
that the job wants to run in idle-maintenance period.

Bug: 26851107

Change-Id: I97f5286bd4d5ca18fbc46c2037c310fd369e18bf

services/core/java/com/android/server/DeviceIdleController.java
services/core/java/com/android/server/job/JobSchedulerService.java
services/core/java/com/android/server/job/StateChangedListener.java
services/core/java/com/android/server/job/controllers/AppIdleController.java
services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java [new file with mode: 0644]
services/core/java/com/android/server/job/controllers/JobStatus.java
services/core/java/com/android/server/job/controllers/StateController.java

index c8763b1..b57e14e 100644 (file)
@@ -198,7 +198,6 @@ public class DeviceIdleController extends SystemService
 
     private int mActiveIdleOpCount;
     private IBinder mDownloadServiceActive;
-    private boolean mSyncActive;
     private boolean mJobsActive;
     private boolean mAlarmsActive;
     private boolean mReportedMaintenanceActivity;
@@ -944,7 +943,7 @@ public class DeviceIdleController extends SystemService
                                 null, mIdleStartedDoneReceiver, null, 0, null, null);
                     }
                     // Always start with one active op for the message being sent here.
-                    // Now we we done!
+                    // Now we are done!
                     decActiveIdleOps();
                     EventLogTags.writeDeviceIdleOffComplete();
                 } break;
@@ -1145,10 +1144,6 @@ public class DeviceIdleController extends SystemService
             setNetworkPolicyTempWhitelistCallbackInternal(callback);
         }
 
-        public void setSyncActive(boolean active) {
-            DeviceIdleController.this.setSyncActive(active);
-        }
-
         public void setJobsActive(boolean active) {
             DeviceIdleController.this.setJobsActive(active);
         }
@@ -1157,6 +1152,16 @@ public class DeviceIdleController extends SystemService
         public void setAlarmsActive(boolean active) {
             DeviceIdleController.this.setAlarmsActive(active);
         }
+
+        /**
+         * Returns the array of app ids whitelisted by user. Take care not to
+         * modify this, as it is a reference to the original copy. But the reference
+         * can change when the list changes, so it needs to be re-acquired when
+         * {@link PowerManager#ACTION_POWER_SAVE_WHITELIST_CHANGED} is sent.
+         */
+        public int[] getPowerSaveWhitelistUserAppIds() {
+            return DeviceIdleController.this.getPowerSaveWhitelistUserAppIds();
+        }
     }
 
     public DeviceIdleController(Context context) {
@@ -1165,6 +1170,12 @@ public class DeviceIdleController extends SystemService
         mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
     }
 
+    int[] getPowerSaveWhitelistUserAppIds() {
+        synchronized (this) {
+            return mPowerSaveWhitelistUserAppIdArray;
+        }
+    }
+
     private static File getSystemDir() {
         return new File(Environment.getDataDirectory(), "system");
     }
@@ -1288,7 +1299,6 @@ public class DeviceIdleController extends SystemService
 
                 mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
                 mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
-
                 mDisplayManager.registerDisplayListener(mDisplayListener, null);
                 updateDisplayLocked();
             }
@@ -1877,16 +1887,6 @@ public class DeviceIdleController extends SystemService
         }
     }
 
-    void setSyncActive(boolean active) {
-        synchronized (this) {
-            mSyncActive = active;
-            reportMaintenanceActivityIfNeededLocked();
-            if (!active) {
-                exitMaintenanceEarlyIfNeededLocked();
-            }
-        }
-    }
-
     void setJobsActive(boolean active) {
         synchronized (this) {
             mJobsActive = active;
@@ -1920,7 +1920,7 @@ public class DeviceIdleController extends SystemService
     }
 
     void reportMaintenanceActivityIfNeededLocked() {
-        boolean active = mJobsActive | mSyncActive | (mDownloadServiceActive != null);
+        boolean active = mJobsActive | (mDownloadServiceActive != null);
         if (active == mReportedMaintenanceActivity) {
             return;
         }
@@ -1933,7 +1933,7 @@ public class DeviceIdleController extends SystemService
     void exitMaintenanceEarlyIfNeededLocked() {
         if (mState == STATE_IDLE_MAINTENANCE || mLightState == LIGHT_STATE_IDLE_MAINTENANCE) {
             if (mActiveIdleOpCount <= 0 && mDownloadServiceActive == null
-                    && !mSyncActive && !mJobsActive && !mAlarmsActive) {
+                    && !mJobsActive && !mAlarmsActive) {
                 final long now = SystemClock.elapsedRealtime();
                 if (DEBUG) {
                     StringBuilder sb = new StringBuilder();
@@ -2741,9 +2741,6 @@ public class DeviceIdleController extends SystemService
                 TimeUtils.formatDuration(mMaintenanceStartTime, SystemClock.elapsedRealtime(), pw);
                 pw.println();
             }
-            if (mSyncActive) {
-                pw.print("  mSyncActive="); pw.println(mSyncActive);
-            }
             if (mJobsActive) {
                 pw.print("  mJobsActive="); pw.println(mJobsActive);
             }
index e34fb9b..6b00f5f 100644 (file)
@@ -67,6 +67,7 @@ import com.android.server.job.controllers.AppIdleController;
 import com.android.server.job.controllers.BatteryController;
 import com.android.server.job.controllers.ConnectivityController;
 import com.android.server.job.controllers.ContentObserverController;
+import com.android.server.job.controllers.DeviceIdleJobsController;
 import com.android.server.job.controllers.IdleController;
 import com.android.server.job.controllers.JobStatus;
 import com.android.server.job.controllers.StateController;
@@ -164,11 +165,6 @@ public final class JobSchedulerService extends com.android.server.SystemService
     boolean mReadyToRock;
 
     /**
-     * True when in device idle mode, so we don't want to schedule any jobs.
-     */
-    boolean mDeviceIdleMode;
-
-    /**
      * What we last reported to DeviceIdleController about whether we are active.
      */
     boolean mReportedActive;
@@ -228,12 +224,6 @@ public final class JobSchedulerService extends com.android.server.SystemService
                     Slog.d(TAG, "Removing jobs for user: " + userId);
                 }
                 cancelJobsForUser(userId);
-            } else if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())
-                    || PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())) {
-                updateIdleMode(mPowerManager != null
-                        ? (mPowerManager.isDeviceIdleMode()
-                        || mPowerManager.isLightDeviceIdleMode())
-                        : false);
             }
         }
     };
@@ -418,44 +408,29 @@ public final class JobSchedulerService extends com.android.server.SystemService
         }
     }
 
-    void updateIdleMode(boolean enabled) {
-        boolean changed = false;
-        boolean rocking;
+    @Override
+    public void onDeviceIdleStateChanged(boolean deviceIdle) {
         synchronized (mLock) {
-            if (mDeviceIdleMode != enabled) {
-                changed = true;
-            }
-            rocking = mReadyToRock;
-        }
-        if (changed) {
-            if (rocking) {
-                for (int i=0; i<mControllers.size(); i++) {
-                    mControllers.get(i).deviceIdleModeChanged(enabled);
-                }
-            }
-            synchronized (mLock) {
-                mDeviceIdleMode = enabled;
-                if (enabled) {
-                    // When becoming idle, make sure no jobs are actively running.
-                    for (int i=0; i<mActiveServices.size(); i++) {
-                        JobServiceContext jsc = mActiveServices.get(i);
-                        final JobStatus executing = jsc.getRunningJob();
-                        if (executing != null) {
-                            jsc.cancelExecutingJob(JobParameters.REASON_DEVICE_IDLE);
-                        }
+            if (deviceIdle) {
+                // When becoming idle, make sure no jobs are actively running.
+                for (int i=0; i<mActiveServices.size(); i++) {
+                    JobServiceContext jsc = mActiveServices.get(i);
+                    final JobStatus executing = jsc.getRunningJob();
+                    if (executing != null) {
+                        jsc.cancelExecutingJob(JobParameters.REASON_DEVICE_IDLE);
                     }
-                } else {
-                    // When coming out of idle, allow thing to start back up.
-                    if (rocking) {
-                        if (mLocalDeviceIdleController != null) {
-                            if (!mReportedActive) {
-                                mReportedActive = true;
-                                mLocalDeviceIdleController.setJobsActive(true);
-                            }
+                }
+            } else {
+                // When coming out of idle, allow thing to start back up.
+                if (mReadyToRock) {
+                    if (mLocalDeviceIdleController != null) {
+                        if (!mReportedActive) {
+                            mReportedActive = true;
+                            mLocalDeviceIdleController.setJobsActive(true);
                         }
                     }
-                    mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
                 }
+                mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
             }
         }
     }
@@ -500,6 +475,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
         mControllers.add(BatteryController.get(this));
         mControllers.add(AppIdleController.get(this));
         mControllers.add(ContentObserverController.get(this));
+        mControllers.add(DeviceIdleJobsController.get(this));
 
         mHandler = new JobHandler(context.getMainLooper());
         mJobSchedulerStub = new JobSchedulerStub();
@@ -521,8 +497,6 @@ public final class JobSchedulerService extends com.android.server.SystemService
             getContext().registerReceiverAsUser(
                     mBroadcastReceiver, UserHandle.ALL, filter, null, null);
             final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
-            userFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
-            userFilter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
             getContext().registerReceiverAsUser(
                     mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
             mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
@@ -553,7 +527,6 @@ public final class JobSchedulerService extends com.android.server.SystemService
                     public void process(JobStatus job) {
                         for (int controller = 0; controller < mControllers.size(); controller++) {
                             final StateController sc = mControllers.get(controller);
-                            sc.deviceIdleModeChanged(mDeviceIdleMode);
                             sc.maybeStartTrackingJobLocked(job, null);
                         }
                     }
@@ -1015,10 +988,6 @@ public final class JobSchedulerService extends com.android.server.SystemService
          */
         private void maybeRunPendingJobsH() {
             synchronized (mLock) {
-                if (mDeviceIdleMode) {
-                    // If device is idle, we will not schedule jobs to run.
-                    return;
-                }
                 if (DEBUG) {
                     Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
                 }
@@ -1188,6 +1157,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
          * Returns a list of all pending jobs. A running job is not considered pending. Periodic
          * jobs are always considered pending.
          */
+        @Override
         public List<JobInfo> getSystemScheduledPendingJobs() {
             synchronized (mLock) {
                 final List<JobInfo> pendingJobs = new ArrayList<JobInfo>();
@@ -1509,7 +1479,6 @@ public final class JobSchedulerService extends com.android.server.SystemService
             }
             pw.println();
             pw.print("mReadyToRock="); pw.println(mReadyToRock);
-            pw.print("mDeviceIdleMode="); pw.println(mDeviceIdleMode);
             pw.print("mReportedActive="); pw.println(mReportedActive);
             pw.print("mMaxActiveJobs="); pw.println(mMaxActiveJobs);
         }
index 97dfad3..87bfc27 100644 (file)
@@ -37,4 +37,6 @@ public interface StateChangedListener {
      *                  indicates to the scheduler that any ready jobs should be flushed.</strong>
      */
     public void onRunJobNow(JobStatus jobStatus);
+
+    public void onDeviceIdleStateChanged(boolean deviceIdle);
 }
index f0c579f..2114fc3 100644 (file)
@@ -87,7 +87,7 @@ public class AppIdleController extends StateController {
         pw.println("Parole On: " + mAppIdleParoleOn);
         for (JobStatus task : mTrackedTasks) {
             pw.print(task.getSourcePackageName());
-            pw.print(":idle="
+            pw.print(":runnable="
                     + ((task.satisfiedConstraints&JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0));
             pw.print(", ");
         }
diff --git a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
new file mode 100644 (file)
index 0000000..d2ef6a2
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.job.controllers;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.server.DeviceIdleController;
+import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerService;
+import com.android.server.job.StateChangedListener;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * When device is dozing, set constraint for all jobs, except whitelisted apps, as not satisfied.
+ * When device is not dozing, set constraint for all jobs as satisfied.
+ */
+public class DeviceIdleJobsController extends StateController {
+
+    private static final String LOG_TAG = "DeviceIdleJobsController";
+    private static final boolean LOG_DEBUG = false;
+
+    // Singleton factory
+    private static Object sCreationLock = new Object();
+    final ArrayList<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
+    private static DeviceIdleJobsController sController;
+
+    private final PowerManager mPowerManager;
+    private final DeviceIdleController.LocalService mLocalDeviceIdleController;
+
+    /**
+     * True when in device idle mode, so we don't want to schedule any jobs.
+     */
+    private boolean mDeviceIdleMode;
+    private int[] mDeviceIdleWhitelistAppIds;
+
+    /**
+     * Returns a singleton for the DeviceIdleJobsController
+     */
+    public static DeviceIdleJobsController get(JobSchedulerService service) {
+        synchronized (sCreationLock) {
+            if (sController == null) {
+                sController = new DeviceIdleJobsController(service, service.getContext(),
+                        service.getLock());
+            }
+            return sController;
+        }
+    }
+
+    // onReceive
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED.equals(action)
+                    || PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
+                updateIdleMode(mPowerManager != null
+                        ? (mPowerManager.isDeviceIdleMode()
+                                || mPowerManager.isLightDeviceIdleMode())
+                        : false);
+            } else if (PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED.equals(action)) {
+                updateWhitelist();
+            }
+        }
+    };
+
+    private DeviceIdleJobsController(StateChangedListener stateChangedListener, Context context,
+            Object lock) {
+        super(stateChangedListener, context, lock);
+
+        // Register for device idle mode changes
+        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mLocalDeviceIdleController =
+                LocalServices.getService(DeviceIdleController.LocalService.class);
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
+        filter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
+        mContext.registerReceiverAsUser(
+                mBroadcastReceiver, UserHandle.ALL, filter, null, null);
+    }
+
+    void updateIdleMode(boolean enabled) {
+        boolean changed = false;
+        // Need the whitelist to be ready when going into idle
+        if (mDeviceIdleWhitelistAppIds == null) {
+            updateWhitelist();
+        }
+        synchronized (mLock) {
+            if (mDeviceIdleMode != enabled) {
+                changed = true;
+            }
+            mDeviceIdleMode = enabled;
+            if (LOG_DEBUG) Slog.d(LOG_TAG, "mDeviceIdleMode=" + mDeviceIdleMode);
+            for (JobStatus task : mTrackedTasks) {
+                updateTaskStateLocked(task);
+            }
+        }
+        // Inform the job scheduler service about idle mode changes
+        if (changed) {
+            mStateChangedListener.onDeviceIdleStateChanged(enabled);
+        }
+    }
+
+    /**
+     * Fetches the latest whitelist from the device idle controller.
+     */
+    void updateWhitelist() {
+        synchronized (mLock) {
+            if (mLocalDeviceIdleController != null) {
+                mDeviceIdleWhitelistAppIds =
+                        mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
+                if (LOG_DEBUG) {
+                    Slog.d(LOG_TAG, "Got whitelist " + Arrays.toString(mDeviceIdleWhitelistAppIds));
+                }
+            }
+        }
+    }
+
+    /**
+     * Checks if the given job's scheduling app id exists in the device idle user whitelist.
+     */
+    boolean isWhitelistedLocked(JobStatus job) {
+        if (mDeviceIdleWhitelistAppIds != null
+                && ArrayUtils.contains(mDeviceIdleWhitelistAppIds,
+                        UserHandle.getAppId(job.getSourceUid()))) {
+            return true;
+        }
+        return false;
+    }
+
+    private void updateTaskStateLocked(JobStatus task) {
+        boolean enableTask = !mDeviceIdleMode || isWhitelistedLocked(task);
+        task.setDeviceNotDozingConstraintSatisfied(enableTask);
+    }
+
+    @Override
+    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
+        synchronized (mLock) {
+            mTrackedTasks.add(jobStatus);
+            updateTaskStateLocked(jobStatus);
+        }
+    }
+
+    @Override
+    public void maybeStopTrackingJobLocked(JobStatus jobStatus, boolean forUpdate) {
+        mTrackedTasks.remove(jobStatus);
+    }
+
+    @Override
+    public void dumpControllerStateLocked(PrintWriter pw) {
+        pw.println("DeviceIdleJobsController");
+        for (JobStatus task : mTrackedTasks) {
+            pw.print(task.getSourcePackageName());
+            pw.print(":runnable="
+                    + ((task.satisfiedConstraints & JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0));
+            pw.print(", ");
+        }
+        pw.println();
+    }
+}
\ No newline at end of file
index 4a2c88c..39905d8 100644 (file)
@@ -54,6 +54,7 @@ public final class JobStatus {
     static final int CONSTRAINT_CONNECTIVITY = 1<<5;
     static final int CONSTRAINT_APP_NOT_IDLE = 1<<6;
     static final int CONSTRAINT_CONTENT_TRIGGER = 1<<7;
+    static final int CONSTRAINT_DEVICE_NOT_DOZING = 1<<8;
 
     // Soft override: ignore constraints like time that don't affect API availability
     public static final int OVERRIDE_SOFT = 1;
@@ -363,6 +364,10 @@ public final class JobStatus {
         return setConstraintSatisfied(CONSTRAINT_CONTENT_TRIGGER, state);
     }
 
+    boolean setDeviceNotDozingConstraintSatisfied(boolean state) {
+        return setConstraintSatisfied(CONSTRAINT_DEVICE_NOT_DOZING, state);
+    }
+
     boolean setConstraintSatisfied(int constraint, boolean state) {
         boolean old = (satisfiedConstraints&constraint) != 0;
         if (old == state) {
@@ -380,11 +385,14 @@ public final class JobStatus {
         // Deadline constraint trumps other constraints (except for periodic jobs where deadline
         // is an implementation detail. A periodic job should only run if its constraints are
         // satisfied).
-        // AppNotIdle implicit constraint trumps all!
+        // AppNotIdle implicit constraint must be satisfied
+        // DeviceNotDozing implicit constraint must be satisfied
         return (isConstraintsSatisfied()
                 || (!job.isPeriodic()
-                && hasDeadlineConstraint() && (satisfiedConstraints&CONSTRAINT_DEADLINE) != 0))
-                && (satisfiedConstraints&CONSTRAINT_APP_NOT_IDLE) != 0;
+                && hasDeadlineConstraint() && (satisfiedConstraints&CONSTRAINT_DEADLINE) != 0)
+                )
+                && (satisfiedConstraints & CONSTRAINT_APP_NOT_IDLE) != 0
+                && (satisfiedConstraints & CONSTRAINT_DEVICE_NOT_DOZING) != 0;
     }
 
     static final int CONSTRAINTS_OF_INTEREST =
@@ -433,6 +441,7 @@ public final class JobStatus {
                 + ",U=" + (job.getTriggerContentUris() != null)
                 + ",F=" + numFailures + ",P=" + job.isPersisted()
                 + ",ANI=" + ((satisfiedConstraints&CONSTRAINT_APP_NOT_IDLE) != 0)
+                + ",DND=" + ((satisfiedConstraints&CONSTRAINT_DEVICE_NOT_DOZING) != 0)
                 + (isReady() ? "(READY)" : "")
                 + "]";
     }
@@ -492,6 +501,9 @@ public final class JobStatus {
         if ((constraints&CONSTRAINT_CONTENT_TRIGGER) != 0) {
             pw.print(" CONTENT_TRIGGER");
         }
+        if ((constraints&CONSTRAINT_DEVICE_NOT_DOZING) != 0) {
+            pw.print(" DEVICE_NOT_DOZING");
+        }
     }
 
     // Dumpsys infrastructure
index 7882bc4..ac7f4c3 100644 (file)
@@ -33,7 +33,6 @@ public abstract class StateController {
     protected final Context mContext;
     protected final Object mLock;
     protected final StateChangedListener mStateChangedListener;
-    protected boolean mDeviceIdleMode;
 
     public StateController(StateChangedListener stateChangedListener, Context context,
             Object lock) {
@@ -42,10 +41,6 @@ public abstract class StateController {
         mLock = lock;
     }
 
-    public void deviceIdleModeChanged(boolean enabled) {
-        mDeviceIdleMode = enabled;
-    }
-
     /**
      * Implement the logic here to decide whether a job should be tracked by this controller.
      * This logic is put here so the JobManager can be completely agnostic of Controller logic.