private int mActiveIdleOpCount;
private IBinder mDownloadServiceActive;
- private boolean mSyncActive;
private boolean mJobsActive;
private boolean mAlarmsActive;
private boolean mReportedMaintenanceActivity;
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;
setNetworkPolicyTempWhitelistCallbackInternal(callback);
}
- public void setSyncActive(boolean active) {
- DeviceIdleController.this.setSyncActive(active);
- }
-
public void setJobsActive(boolean active) {
DeviceIdleController.this.setJobsActive(active);
}
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) {
mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
}
+ int[] getPowerSaveWhitelistUserAppIds() {
+ synchronized (this) {
+ return mPowerSaveWhitelistUserAppIdArray;
+ }
+ }
+
private static File getSystemDir() {
return new File(Environment.getDataDirectory(), "system");
}
mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
-
mDisplayManager.registerDisplayListener(mDisplayListener, null);
updateDisplayLocked();
}
}
}
- void setSyncActive(boolean active) {
- synchronized (this) {
- mSyncActive = active;
- reportMaintenanceActivityIfNeededLocked();
- if (!active) {
- exitMaintenanceEarlyIfNeededLocked();
- }
- }
- }
-
void setJobsActive(boolean active) {
synchronized (this) {
mJobsActive = active;
}
void reportMaintenanceActivityIfNeededLocked() {
- boolean active = mJobsActive | mSyncActive | (mDownloadServiceActive != null);
+ boolean active = mJobsActive | (mDownloadServiceActive != null);
if (active == mReportedMaintenanceActivity) {
return;
}
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();
TimeUtils.formatDuration(mMaintenanceStartTime, SystemClock.elapsedRealtime(), pw);
pw.println();
}
- if (mSyncActive) {
- pw.print(" mSyncActive="); pw.println(mSyncActive);
- }
if (mJobsActive) {
pw.print(" mJobsActive="); pw.println(mJobsActive);
}
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;
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;
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);
}
}
};
}
}
- 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();
}
}
}
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();
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);
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);
}
}
*/
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.");
}
* 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>();
}
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);
}
* indicates to the scheduler that any ready jobs should be flushed.</strong>
*/
public void onRunJobNow(JobStatus jobStatus);
+
+ public void onDeviceIdleStateChanged(boolean deviceIdle);
}
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(", ");
}
--- /dev/null
+/*
+ * 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
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;
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) {
// 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 =
+ ",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)" : "")
+ "]";
}
if ((constraints&CONSTRAINT_CONTENT_TRIGGER) != 0) {
pw.print(" CONTENT_TRIGGER");
}
+ if ((constraints&CONSTRAINT_DEVICE_NOT_DOZING) != 0) {
+ pw.print(" DEVICE_NOT_DOZING");
+ }
}
// Dumpsys infrastructure
protected final Context mContext;
protected final Object mLock;
protected final StateChangedListener mStateChangedListener;
- protected boolean mDeviceIdleMode;
public StateController(StateChangedListener stateChangedListener, Context context,
Object lock) {
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.