Merge "Move device idle logic into a job StateController" into nyc-dev

This commit is contained in:
Amith Yamasani
2016-03-23 17:32:05 +00:00
committed by Android (Google) Code Review
7 changed files with 242 additions and 83 deletions

View 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);
}

View 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;
@@ -163,11 +164,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.
*/
@@ -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);
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);
}
}
}
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);
} else {
// When coming out of idle, allow thing to start back up.
if (mReadyToRock) {
if (mLocalDeviceIdleController != null) {
if (!mReportedActive) {
mReportedActive = true;
mLocalDeviceIdleController.setJobsActive(true);
}
}
} else {
// When coming out of idle, allow thing to start back up.
if (rocking) {
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);
}

View 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);
}

View 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(", ");
}

View File

@@ -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();
}
}

View 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

View 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.