diff options
author | Christopher Tate <ctate@google.com> | 2014-06-16 15:51:39 -0700 |
---|---|---|
committer | Christopher Tate <ctate@google.com> | 2014-06-17 12:14:45 -0700 |
commit | cf1a2f73fc102be2ac7060ac97d4682bb2565ca5 (patch) | |
tree | f00e01d283dbf43199d2d3692d578b7e18e9326c /services | |
parent | 6b2df21ecacfa6826a85cabdf8d6fe0e81fe11d9 (diff) | |
download | frameworks_base-cf1a2f73fc102be2ac7060ac97d4682bb2565ca5.zip frameworks_base-cf1a2f73fc102be2ac7060ac97d4682bb2565ca5.tar.gz frameworks_base-cf1a2f73fc102be2ac7060ac97d4682bb2565ca5.tar.bz2 |
Switch everything to scheduled jobs
Everything that used the IdleMaintenance APIs/broadcasts gets to use the
spiffy new JobScheduler instead. Hooray!
On top of that, the now-obsolete "idle maintenance" APIs are now gone
entirely. Double hooray!
Bug 14993295
Change-Id: I5fb67c296ca8cd0ba8a2c8760a0f0d9d962d813b
Diffstat (limited to 'services')
3 files changed, 36 insertions, 859 deletions
diff --git a/services/core/java/com/android/server/IdleMaintenanceService.java b/services/core/java/com/android/server/IdleMaintenanceService.java deleted file mode 100644 index acc6abe..0000000 --- a/services/core/java/com/android/server/IdleMaintenanceService.java +++ /dev/null @@ -1,818 +0,0 @@ -/* - * Copyright (C) 2013 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; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.app.maintenance.IIdleCallback; -import android.app.maintenance.IIdleService; -import android.app.maintenance.IdleService; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.ServiceConnection; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.os.Handler; -import android.os.PowerManager; -import android.os.PowerManager.WakeLock; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.Process; -import android.os.RemoteException; -import android.os.SystemClock; -import android.os.UserHandle; -import android.os.WorkSource; -import android.util.ArrayMap; -import android.util.Log; -import android.util.Slog; -import android.util.SparseArray; - -import java.util.LinkedList; -import java.util.List; -import java.util.Random; - -/** - * This service observes the device state and when applicable sends - * broadcasts at the beginning and at the end of a period during which - * observers can perform idle maintenance tasks. Typical use of the - * idle maintenance is to perform somehow expensive tasks that can be - * postponed to a moment when they will not degrade user experience. - * - * The current implementation is very simple. The start of a maintenance - * window is announced if: the screen is off or showing a dream AND the - * battery level is more than twenty percent AND at least one hour passed - * activity). - * - * The end of a maintenance window is announced only if: a start was - * announced AND the screen turned on or a dream was stopped. - * - * Method naming note: - * Methods whose name ends with "Tm" must only be called from the main thread. - */ -public class IdleMaintenanceService extends BroadcastReceiver { - - private static final boolean DEBUG = false; - - private static final String TAG = IdleMaintenanceService.class.getSimpleName(); - - private static final int LAST_USER_ACTIVITY_TIME_INVALID = -1; - - private static final long MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS = 24 * 60 * 60 * 1000; // 1 day - - private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING = 30; // percent - - private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_NOT_CHARGING = 80; // percent - - private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_RUNNING = 20; // percent - - private static final long MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START = 71 * 60 * 1000; // 71 min - - private static final long MAX_IDLE_MAINTENANCE_DURATION = 71 * 60 * 1000; // 71 min - - private static final String ACTION_UPDATE_IDLE_MAINTENANCE_STATE = - "com.android.server.IdleMaintenanceService.action.UPDATE_IDLE_MAINTENANCE_STATE"; - - private static final String ACTION_FORCE_IDLE_MAINTENANCE = - "com.android.server.IdleMaintenanceService.action.FORCE_IDLE_MAINTENANCE"; - - static final int MSG_OP_COMPLETE = 1; - static final int MSG_IDLE_FINISHED = 2; - static final int MSG_TIMEOUT = 3; - - // when a timeout happened, what were we expecting? - static final int VERB_BINDING = 1; - static final int VERB_IDLING = 2; - static final int VERB_ENDING = 3; - - // What are our relevant timeouts / allocated slices? - static final long OP_TIMEOUT = 8 * 1000; // 8 seconds to bind or ack the start - static final long IDLE_TIMESLICE = 10 * 60 * 1000; // ten minutes for each idler - - private final AlarmManager mAlarmService; - private final BatteryService mBatteryService; - private final PendingIntent mUpdateIdleMaintenanceStatePendingIntent; - private final Context mContext; - private final WakeLock mWakeLock; - private final WorkSource mSystemWorkSource = new WorkSource(Process.myUid()); - - private long mLastIdleMaintenanceStartTimeMillis; - private long mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID; - private boolean mIdleMaintenanceStarted; - - final IdleCallback mCallback; - final Handler mHandler; - - final Random mTokenGenerator = new Random(); - - int makeToken() { - int token; - do { - token = mTokenGenerator.nextInt(Integer.MAX_VALUE); - } while (token == 0); - return token; - } - - class ActiveTask { - public IdleServiceInfo who; - public int verb; - public int token; - - ActiveTask(IdleServiceInfo target, int action) { - who = target; - verb = action; - token = makeToken(); - } - - @Override - public String toString() { - return "ActiveTask{" + Integer.toHexString(this.hashCode()) - + " : verb=" + verb - + " : token=" + token - + " : "+ who + "}"; - } - } - - // What operations are in flight? - final SparseArray<ActiveTask> mPendingOperations = new SparseArray<ActiveTask>(); - - // Idle service queue management - class IdleServiceInfo { - public final ComponentName componentName; - public final int uid; - public IIdleService service; - - IdleServiceInfo(ResolveInfo info, ComponentName cname) { - componentName = cname; // derived from 'info' but this avoids an extra object - uid = info.serviceInfo.applicationInfo.uid; - service = null; - } - - @Override - public int hashCode() { - return componentName.hashCode(); - } - - @Override - public String toString() { - return "IdleServiceInfo{" + componentName - + " / " + (service == null ? "null" : service.asBinder()) + "}"; - } - } - - final ArrayMap<ComponentName, IdleServiceInfo> mIdleServices = - new ArrayMap<ComponentName, IdleServiceInfo>(); - final LinkedList<IdleServiceInfo> mIdleServiceQueue = new LinkedList<IdleServiceInfo>(); - IdleServiceInfo mCurrentIdler; // set when we've committed to launching an idler - IdleServiceInfo mLastIdler; // end of queue when idling begins - - void reportNoTimeout(int token, boolean result) { - final Message msg = mHandler.obtainMessage(MSG_OP_COMPLETE, result ? 1 : 0, token); - mHandler.sendMessage(msg); - } - - // Binder acknowledgment trampoline - class IdleCallback extends IIdleCallback.Stub { - @Override - public void acknowledgeStart(int token, boolean result) throws RemoteException { - reportNoTimeout(token, result); - } - - @Override - public void acknowledgeStop(int token) throws RemoteException { - reportNoTimeout(token, false); - } - - @Override - public void idleFinished(int token) throws RemoteException { - if (DEBUG) { - Slog.v(TAG, "idleFinished: " + token); - } - final Message msg = mHandler.obtainMessage(MSG_IDLE_FINISHED, 0, token); - mHandler.sendMessage(msg); - } - } - - // Stuff that we run on a Handler - class IdleHandler extends Handler { - public IdleHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - final int token = msg.arg2; - - switch (msg.what) { - case MSG_OP_COMPLETE: { - if (DEBUG) { - Slog.i(TAG, "MSG_OP_COMPLETE of " + token); - } - ActiveTask task = mPendingOperations.get(token); - if (task != null) { - mPendingOperations.remove(token); - removeMessages(MSG_TIMEOUT); - - handleOpCompleteTm(task, msg.arg1); - } else { - // Can happen in a race between timeout and actual - // (belated) completion of a "begin idling" or similar - // operation. In that state we've already processed the - // timeout, so we intentionally no-op here. - if (DEBUG) { - Slog.w(TAG, "Belated op-complete of " + token); - } - } - break; - } - - case MSG_IDLE_FINISHED: { - if (DEBUG) { - Slog.i(TAG, "MSG_IDLE_FINISHED of " + token); - } - ActiveTask task = mPendingOperations.get(token); - if (task != null) { - if (DEBUG) { - Slog.i(TAG, "... removing task " + token); - } - mPendingOperations.remove(token); - removeMessages(MSG_TIMEOUT); - - handleIdleFinishedTm(task); - } else { - // Can happen "legitimately" from an app explicitly calling - // idleFinished() after already having been told that its slice - // has ended. - if (DEBUG) { - Slog.w(TAG, "Belated idle-finished of " + token); - } - } - break; - } - - case MSG_TIMEOUT: { - if (DEBUG) { - Slog.i(TAG, "MSG_TIMEOUT of " + token); - } - ActiveTask task = mPendingOperations.get(token); - if (task != null) { - mPendingOperations.remove(token); - removeMessages(MSG_OP_COMPLETE); - - handleTimeoutTm(task); - } else { - // This one should not happen; we flushed timeout messages - // whenever we entered a state after which we have established - // that they are not appropriate. - Slog.w(TAG, "Unexpected timeout of " + token); - } - break; - } - - default: - Slog.w(TAG, "Unknown message: " + msg.what); - } - } - } - - void handleTimeoutTm(ActiveTask task) { - switch (task.verb) { - case VERB_BINDING: { - // We were trying to bind to this service, but it wedged or otherwise - // failed to respond in time. Let it stay in the queue for the next - // time around, but just give up on it for now and go on to the next. - startNextIdleServiceTm(); - break; - } - case VERB_IDLING: { - // The service has reached the end of its designated idle timeslice. - // This is not considered an error. - if (DEBUG) { - Slog.i(TAG, "Idler reached end of timeslice: " + task.who); - } - sendEndIdleTm(task.who); - break; - } - case VERB_ENDING: { - if (mCurrentIdler == task.who) { - if (DEBUG) { - Slog.i(TAG, "Task timed out when ending; unbind needed"); - } - handleIdleFinishedTm(task); - } else { - if (DEBUG) { - Slog.w(TAG, "Ending timeout for non-current idle service!"); - } - } - break; - } - default: { - Slog.w(TAG, "Unknown timeout state " + task.verb); - break; - } - } - } - - void handleOpCompleteTm(ActiveTask task, int result) { - if (DEBUG) { - Slog.i(TAG, "handleOpComplete : task=" + task + " result=" + result); - } - if (task.verb == VERB_IDLING) { - // If the service was told to begin idling and responded positively, then - // it has begun idling and will eventually either explicitly finish, or - // reach the end of its allotted timeslice. It's running free now, so we - // just schedule the idle-expiration timeout under the token it's already been - // given and let it keep going. - if (result != 0) { - scheduleOpTimeoutTm(task); - } else { - // The idle service has indicated that it does not, in fact, - // need to run at present, so we immediately indicate that it's - // to finish idling, and go on to the next idler. - if (DEBUG) { - Slog.i(TAG, "Idler declined idling; moving along"); - } - sendEndIdleTm(task.who); - } - } else { - // In the idling case, the task will be cleared either as the result of a timeout - // or of an explicit idleFinished(). For all other operations (binding, ending) we - // are done with the task as such, so we remove it from our bookkeeping. - if (DEBUG) { - Slog.i(TAG, "Clearing task " + task); - } - mPendingOperations.remove(task.token); - if (task.verb == VERB_ENDING) { - // The last bit of handshaking around idle cessation for this target - handleIdleFinishedTm(task); - } - } - } - - void handleIdleFinishedTm(ActiveTask task) { - final IdleServiceInfo who = task.who; - if (who == mCurrentIdler) { - if (DEBUG) { - Slog.i(TAG, "Current idler has finished: " + who); - Slog.i(TAG, "Attributing wakelock to system work source"); - } - mContext.unbindService(mConnection); - startNextIdleServiceTm(); - } else { - Slog.w(TAG, "finish from non-current idle service? " + who); - } - } - - void updateIdleServiceQueueTm() { - if (DEBUG) { - Slog.i(TAG, "Updating idle service queue"); - } - PackageManager pm = mContext.getPackageManager(); - Intent idleIntent = new Intent(IdleService.SERVICE_INTERFACE); - List<ResolveInfo> services = pm.queryIntentServices(idleIntent, 0); - for (ResolveInfo info : services) { - if (info.serviceInfo != null) { - if (IdleService.PERMISSION_BIND.equals(info.serviceInfo.permission)) { - final ComponentName componentName = new ComponentName( - info.serviceInfo.packageName, - info.serviceInfo.name); - if (DEBUG) { - Slog.i(TAG, " - " + componentName); - } - if (!mIdleServices.containsKey(componentName)) { - if (DEBUG) { - Slog.i(TAG, " + not known; adding"); - } - IdleServiceInfo serviceInfo = new IdleServiceInfo(info, componentName); - mIdleServices.put(componentName, serviceInfo); - mIdleServiceQueue.add(serviceInfo); - } - } else { - if (DEBUG) { - Slog.i(TAG, "Idle service " + info.serviceInfo - + " does not have required permission; ignoring"); - } - } - } - } - } - - void startNextIdleServiceTm() { - mWakeLock.setWorkSource(mSystemWorkSource); - - if (mLastIdler == null) { - // we've run the queue; nothing more to do until the next idle interval. - if (DEBUG) { - Slog.i(TAG, "Queue already drained; nothing more to do"); - } - return; - } - - if (DEBUG) { - Slog.i(TAG, "startNextIdleService : last=" + mLastIdler + " cur=" + mCurrentIdler); - if (mIdleServiceQueue.size() > 0) { - int i = 0; - Slog.i(TAG, "Queue (" + mIdleServiceQueue.size() + "):"); - for (IdleServiceInfo info : mIdleServiceQueue) { - Slog.i(TAG, " " + i + " : " + info); - i++; - } - } - } - if (mCurrentIdler != mLastIdler) { - if (mIdleServiceQueue.size() > 0) { - IdleServiceInfo target = mIdleServiceQueue.pop(); - if (DEBUG) { - Slog.i(TAG, "starting next idle service " + target); - } - Intent idleIntent = new Intent(IdleService.SERVICE_INTERFACE); - idleIntent.setComponent(target.componentName); - mCurrentIdler = target; - ActiveTask task = new ActiveTask(target, VERB_BINDING); - scheduleOpTimeoutTm(task); - boolean bindOk = mContext.bindServiceAsUser(idleIntent, mConnection, - Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY, UserHandle.OWNER); - if (!bindOk) { - if (DEBUG) { - Slog.w(TAG, "bindService() to " + target.componentName - + " failed"); - } - } else { - mIdleServiceQueue.add(target); // at the end for next time - if (DEBUG) { Slog.i(TAG, "Attributing wakelock to target uid " + target.uid); } - mWakeLock.setWorkSource(new WorkSource(target.uid)); - } - } else { - // Queue is empty but mLastIdler is non-null -- eeep. Clear *everything* - // and wind up until the next time around. - Slog.e(TAG, "Queue unexpectedly empty; resetting. last=" - + mLastIdler + " cur=" + mCurrentIdler); - mHandler.removeMessages(MSG_TIMEOUT); - mPendingOperations.clear(); - stopIdleMaintenanceTm(); - } - } else { - // we've reached the place we started, so mark the queue as drained - if (DEBUG) { - Slog.i(TAG, "Reached end of queue."); - } - stopIdleMaintenanceTm(); - } - } - - void sendStartIdleTm(IdleServiceInfo who) { - ActiveTask task = new ActiveTask(who, VERB_IDLING); - scheduleOpTimeoutTm(task); - try { - who.service.startIdleMaintenance(mCallback, task.token); - } catch (RemoteException e) { - // We bound to it, but now we can't reach it. Bail and go on to the - // next service. - mContext.unbindService(mConnection); - if (DEBUG) { Slog.i(TAG, "Attributing wakelock to system work source"); } - mHandler.removeMessages(MSG_TIMEOUT); - startNextIdleServiceTm(); - } - } - - void sendEndIdleTm(IdleServiceInfo who) { - ActiveTask task = new ActiveTask(who, VERB_ENDING); - scheduleOpTimeoutTm(task); - if (DEBUG) { - Slog.i(TAG, "Sending end-idle to " + who); - } - try { - who.service.stopIdleMaintenance(mCallback, task.token); - } catch (RemoteException e) { - // We bound to it, but now we can't reach it. Bail and go on to the - // next service. - mContext.unbindService(mConnection); - if (DEBUG) { Slog.i(TAG, "Attributing wakelock to system work source"); } - mHandler.removeMessages(MSG_TIMEOUT); - startNextIdleServiceTm(); - } - } - - ServiceConnection mConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - if (DEBUG) { - Slog.i(TAG, "onServiceConnected(" + name + ")"); - } - IdleServiceInfo info = mIdleServices.get(name); - if (info != null) { - // Bound! Cancel the bind timeout - mHandler.removeMessages(MSG_TIMEOUT); - // Now tell it to start its idle work - info.service = IIdleService.Stub.asInterface(service); - sendStartIdleTm(info); - } else { - // We bound to a service we don't know about. That's ungood. - Slog.e(TAG, "Connected to unexpected component " + name); - mContext.unbindService(this); - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - if (DEBUG) { - Slog.i(TAG, "onServiceDisconnected(" + name + ")"); - } - IdleServiceInfo who = mIdleServices.get(name); - if (who == mCurrentIdler) { - // Hm, okay; they didn't tell us they were finished but they - // went away. Crashed, probably. Oh well. They're gone, so - // we can't finish them cleanly; just force things along. - Slog.w(TAG, "Idler unexpectedly vanished: " + mCurrentIdler); - mContext.unbindService(this); - mHandler.removeMessages(MSG_TIMEOUT); - startNextIdleServiceTm(); - } else { - // Not the current idler, so we don't interrupt our process... - if (DEBUG) { - Slog.w(TAG, "Disconnect of abandoned or unexpected service " + name); - } - } - } - }; - - // Schedules a timeout / end-of-work based on the task verb - void scheduleOpTimeoutTm(ActiveTask task) { - final long timeoutMillis = (task.verb == VERB_IDLING) ? IDLE_TIMESLICE : OP_TIMEOUT; - if (DEBUG) { - Slog.i(TAG, "Scheduling timeout (token " + task.token - + " : verb " + task.verb + ") for " + task + " in " + timeoutMillis); - } - mPendingOperations.put(task.token, task); - mHandler.removeMessages(MSG_TIMEOUT); - final Message msg = mHandler.obtainMessage(MSG_TIMEOUT, 0, task.token); - mHandler.sendMessageDelayed(msg, timeoutMillis); - } - - // ------------------------------------------------------------------------------- - public IdleMaintenanceService(Context context, BatteryService batteryService) { - mContext = context; - mBatteryService = batteryService; - - mAlarmService = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - - PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); - - mHandler = new IdleHandler(mContext.getMainLooper()); - mCallback = new IdleCallback(); - - Intent intent = new Intent(ACTION_UPDATE_IDLE_MAINTENANCE_STATE); - intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - mUpdateIdleMaintenanceStatePendingIntent = PendingIntent.getBroadcast(mContext, 0, - intent, PendingIntent.FLAG_UPDATE_CURRENT); - - register(mHandler); - } - - public void register(Handler handler) { - IntentFilter intentFilter = new IntentFilter(); - - // Alarm actions. - intentFilter.addAction(ACTION_UPDATE_IDLE_MAINTENANCE_STATE); - - // Battery actions. - intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); - - // Screen actions. - intentFilter.addAction(Intent.ACTION_SCREEN_ON); - intentFilter.addAction(Intent.ACTION_SCREEN_OFF); - - // Dream actions. - intentFilter.addAction(Intent.ACTION_DREAMING_STARTED); - intentFilter.addAction(Intent.ACTION_DREAMING_STOPPED); - - mContext.registerReceiverAsUser(this, UserHandle.ALL, - intentFilter, null, mHandler); - - intentFilter = new IntentFilter(); - intentFilter.addAction(ACTION_FORCE_IDLE_MAINTENANCE); - mContext.registerReceiverAsUser(this, UserHandle.ALL, - intentFilter, android.Manifest.permission.SET_ACTIVITY_WATCHER, mHandler); - } - - private void scheduleUpdateIdleMaintenanceState(long delayMillis) { - final long triggetRealTimeMillis = SystemClock.elapsedRealtime() + delayMillis; - mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggetRealTimeMillis, - mUpdateIdleMaintenanceStatePendingIntent); - } - - private void unscheduleUpdateIdleMaintenanceState() { - mAlarmService.cancel(mUpdateIdleMaintenanceStatePendingIntent); - } - - private void updateIdleMaintenanceStateTm(boolean noisy) { - if (mIdleMaintenanceStarted) { - // Idle maintenance can be interrupted by user activity, or duration - // time out, or low battery. - final boolean batteryOk - = batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning(); - if (!lastUserActivityPermitsIdleMaintenanceRunning() || !batteryOk) { - unscheduleUpdateIdleMaintenanceState(); - mIdleMaintenanceStarted = false; - // We stopped since we don't have enough battery or timed out but the - // user is not using the device, so we should be able to run maintenance - // in the next maintenance window since the battery may be charged - // without interaction and the min interval between maintenances passed. - if (!batteryOk) { - scheduleUpdateIdleMaintenanceState( - getNextIdleMaintenanceIntervalStartFromNow()); - } - - EventLogTags.writeIdleMaintenanceWindowFinish(SystemClock.elapsedRealtime(), - mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(), - isBatteryCharging() ? 1 : 0); - scheduleIdleFinishTm(); - } - } else if (deviceStatePermitsIdleMaintenanceStart(noisy) - && lastUserActivityPermitsIdleMaintenanceStart(noisy) - && lastRunPermitsIdleMaintenanceStart(noisy)) { - // Now that we started idle maintenance, we should schedule another - // update for the moment when the idle maintenance times out. - scheduleUpdateIdleMaintenanceState(MAX_IDLE_MAINTENANCE_DURATION); - mIdleMaintenanceStarted = true; - EventLogTags.writeIdleMaintenanceWindowStart(SystemClock.elapsedRealtime(), - mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(), - isBatteryCharging() ? 1 : 0); - mLastIdleMaintenanceStartTimeMillis = SystemClock.elapsedRealtime(); - startIdleMaintenanceTm(); - } else if (lastUserActivityPermitsIdleMaintenanceStart(noisy)) { - if (lastRunPermitsIdleMaintenanceStart(noisy)) { - // The user does not use the device and we did not run maintenance in more - // than the min interval between runs, so schedule an update - maybe the - // battery will be charged latter. - scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START); - } else { - // The user does not use the device but we have run maintenance in the min - // interval between runs, so schedule an update after the min interval ends. - scheduleUpdateIdleMaintenanceState( - getNextIdleMaintenanceIntervalStartFromNow()); - } - } - } - - void startIdleMaintenanceTm() { - if (DEBUG) { - Slog.i(TAG, "*** Starting idle maintenance ***"); - } - if (DEBUG) { Slog.i(TAG, "Attributing wakelock to system work source"); } - mWakeLock.setWorkSource(mSystemWorkSource); - mWakeLock.acquire(); - updateIdleServiceQueueTm(); - mCurrentIdler = null; - mLastIdler = (mIdleServiceQueue.size() > 0) ? mIdleServiceQueue.peekLast() : null; - startNextIdleServiceTm(); - } - - // Start a graceful wind-down of the idle maintenance state: end the current idler - // and pretend that we've finished running the queue. If there's no current idler, - // this is a no-op. - void scheduleIdleFinishTm() { - if (mCurrentIdler != null) { - if (DEBUG) { - Slog.i(TAG, "*** Finishing idle maintenance ***"); - } - mLastIdler = mCurrentIdler; - sendEndIdleTm(mCurrentIdler); - } else { - if (DEBUG) { - Slog.w(TAG, "Asked to finish idle maintenance but we're done already"); - } - } - } - - // Actual finalization of the idle maintenance sequence - void stopIdleMaintenanceTm() { - if (mLastIdler != null) { - if (DEBUG) { - Slog.i(TAG, "*** Idle maintenance shutdown ***"); - } - mWakeLock.setWorkSource(mSystemWorkSource); - mLastIdler = mCurrentIdler = null; - updateIdleMaintenanceStateTm(false); // resets 'started' and schedules next window - mWakeLock.release(); - } else { - Slog.e(TAG, "ERROR: idle shutdown but invariants not held. last=" + mLastIdler - + " cur=" + mCurrentIdler + " size=" + mIdleServiceQueue.size()); - } - } - - private long getNextIdleMaintenanceIntervalStartFromNow() { - return mLastIdleMaintenanceStartTimeMillis + MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS - - SystemClock.elapsedRealtime(); - } - - private boolean deviceStatePermitsIdleMaintenanceStart(boolean noisy) { - final int minBatteryLevel = isBatteryCharging() - ? MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING - : MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_NOT_CHARGING; - boolean allowed = (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID - && mBatteryService.getBatteryLevel() > minBatteryLevel); - if (!allowed && noisy) { - Slog.i("IdleMaintenance", "Idle maintenance not allowed due to power"); - } - return allowed; - } - - private boolean lastUserActivityPermitsIdleMaintenanceStart(boolean noisy) { - // The last time the user poked the device is above the threshold. - boolean allowed = (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID - && SystemClock.elapsedRealtime() - mLastUserActivityElapsedTimeMillis - > MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START); - if (!allowed && noisy) { - Slog.i("IdleMaintenance", "Idle maintenance not allowed due to last user activity"); - } - return allowed; - } - - private boolean lastRunPermitsIdleMaintenanceStart(boolean noisy) { - // Enough time passed since the last maintenance run. - boolean allowed = SystemClock.elapsedRealtime() - mLastIdleMaintenanceStartTimeMillis - > MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS; - if (!allowed && noisy) { - Slog.i("IdleMaintenance", "Idle maintenance not allowed due time since last"); - } - return allowed; - } - - private boolean lastUserActivityPermitsIdleMaintenanceRunning() { - // The user is not using the device. - return (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID); - } - - private boolean batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning() { - // Battery not too low and the maintenance duration did not timeout. - return (mBatteryService.getBatteryLevel() > MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_RUNNING - && mLastIdleMaintenanceStartTimeMillis + MAX_IDLE_MAINTENANCE_DURATION - > SystemClock.elapsedRealtime()); - } - - private boolean isBatteryCharging() { - return mBatteryService.getPlugType() > 0 - && mBatteryService.getInvalidCharger() == 0; - } - - @Override - public void onReceive(Context context, Intent intent) { - if (DEBUG) { - Log.i(TAG, intent.getAction()); - } - String action = intent.getAction(); - if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { - // We care about battery only if maintenance is in progress so we can - // stop it if battery is too low. Note that here we assume that the - // maintenance clients are properly holding a wake lock. We will - // refactor the maintenance to use services instead of intents for the - // next release. The only client for this for now is internal an holds - // a wake lock correctly. - if (mIdleMaintenanceStarted) { - updateIdleMaintenanceStateTm(false); - } - } else if (Intent.ACTION_SCREEN_ON.equals(action) - || Intent.ACTION_DREAMING_STOPPED.equals(action)) { - mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID; - // Unschedule any future updates since we already know that maintenance - // cannot be performed since the user is back. - unscheduleUpdateIdleMaintenanceState(); - // If the screen went on/stopped dreaming, we know the user is using the - // device which means that idle maintenance should be stopped if running. - updateIdleMaintenanceStateTm(false); - } else if (Intent.ACTION_SCREEN_OFF.equals(action) - || Intent.ACTION_DREAMING_STARTED.equals(action)) { - mLastUserActivityElapsedTimeMillis = SystemClock.elapsedRealtime(); - // If screen went off/started dreaming, we may be able to start idle maintenance - // after the minimal user inactivity elapses. We schedule an alarm for when - // this timeout elapses since the device may go to sleep by then. - scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START); - } else if (ACTION_UPDATE_IDLE_MAINTENANCE_STATE.equals(action)) { - updateIdleMaintenanceStateTm(false); - } else if (ACTION_FORCE_IDLE_MAINTENANCE.equals(action)) { - long now = SystemClock.elapsedRealtime() - 1; - mLastUserActivityElapsedTimeMillis = now - MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START; - mLastIdleMaintenanceStartTimeMillis = now - MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS; - updateIdleMaintenanceStateTm(true); - } - } -} diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index f2db791..2a7b4f6 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -16,12 +16,13 @@ package com.android.server.pm; -import android.content.BroadcastReceiver; +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.os.ServiceManager; -import android.os.UserHandle; import android.util.Log; import java.util.HashSet; @@ -30,62 +31,63 @@ import java.util.concurrent.atomic.AtomicBoolean; /** * {@hide} */ -public class BackgroundDexOptService { - +public class BackgroundDexOptService extends JobService { static final String TAG = "BackgroundDexOptService"; - private final BroadcastReceiver mIdleMaintenanceReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)) { - onIdleStart(); - } else if (Intent.ACTION_IDLE_MAINTENANCE_END.equals(action)) { - onIdleStop(); - } - } - }; - - final PackageManagerService mPackageManager; + static final int BACKGROUND_DEXOPT_JOB = 808; + private static ComponentName sDexoptServiceName = new ComponentName( + BackgroundDexOptService.class.getPackage().getName(), + BackgroundDexOptService.class.getName()); final AtomicBoolean mIdleTime = new AtomicBoolean(false); - public BackgroundDexOptService(Context context) { - mPackageManager = (PackageManagerService)ServiceManager.getService("package"); - - IntentFilter idleMaintenanceFilter = new IntentFilter(); - idleMaintenanceFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_START); - idleMaintenanceFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_END); - context.registerReceiverAsUser(mIdleMaintenanceReceiver, UserHandle.ALL, - idleMaintenanceFilter, null, null); + public static void schedule(Context context) { + JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); + JobInfo job = new JobInfo.Builder(BACKGROUND_DEXOPT_JOB, sDexoptServiceName) + .setRequiresDeviceIdle(true) + .setRequiresCharging(true) + .build(); + js.schedule(job); } - public boolean onIdleStart() { + @Override + public boolean onStartJob(JobParameters params) { Log.i(TAG, "onIdleStart"); - if (mPackageManager.isStorageLow()) { + final PackageManagerService pm = + (PackageManagerService)ServiceManager.getService("package"); + + if (pm.isStorageLow()) { return false; } - final HashSet<String> pkgs = mPackageManager.getPackagesThatNeedDexOpt(); + final HashSet<String> pkgs = pm.getPackagesThatNeedDexOpt(); if (pkgs == null) { return false; } + + final JobParameters jobParams = params; mIdleTime.set(true); new Thread("BackgroundDexOptService_DexOpter") { @Override public void run() { for (String pkg : pkgs) { if (!mIdleTime.get()) { - break; + // stopped while still working, so we need to reschedule + schedule(BackgroundDexOptService.this); + return; } - mPackageManager.performDexOpt(pkg, false); + pm.performDexOpt(pkg, false); } + // ran to completion, so we abandon our timeslice and do not reschedule + jobFinished(jobParams, false); } }.start(); return true; } - public void onIdleStop() { + @Override + public boolean onStopJob(JobParameters params) { Log.i(TAG, "onIdleStop"); mIdleTime.set(false); + return false; } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 3102cce..04ba2a1 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -933,13 +933,6 @@ public final class SystemServer { } try { - Slog.i(TAG, "IdleMaintenanceService"); - new IdleMaintenanceService(context, battery); - } catch (Throwable e) { - reportWtf("starting IdleMaintenanceService", e); - } - - try { if (pm.hasSystemFeature(PackageManager.FEATURE_PRINTING)) { mSystemServiceManager.startService(PRINT_MANAGER_SERVICE_CLASS); } @@ -990,7 +983,7 @@ public final class SystemServer { try { Slog.i(TAG, "BackgroundDexOptService"); - new BackgroundDexOptService(context); + BackgroundDexOptService.schedule(context); } catch (Throwable e) { reportWtf("starting BackgroundDexOptService", e); } |