summaryrefslogtreecommitdiffstats
path: root/services/core/java/com/android/server/IdleMaintenanceService.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/core/java/com/android/server/IdleMaintenanceService.java')
-rw-r--r--services/core/java/com/android/server/IdleMaintenanceService.java327
1 files changed, 327 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/IdleMaintenanceService.java b/services/core/java/com/android/server/IdleMaintenanceService.java
new file mode 100644
index 0000000..b0a1aca
--- /dev/null
+++ b/services/core/java/com/android/server/IdleMaintenanceService.java
@@ -0,0 +1,327 @@
+/*
+ * 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.Activity;
+import android.app.ActivityManagerNative;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+/**
+ * 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.
+ */
+public class IdleMaintenanceService extends BroadcastReceiver {
+
+ private static final boolean DEBUG = false;
+
+ private static final String LOG_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";
+
+ private static final Intent sIdleMaintenanceStartIntent;
+ static {
+ sIdleMaintenanceStartIntent = new Intent(Intent.ACTION_IDLE_MAINTENANCE_START);
+ sIdleMaintenanceStartIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ };
+
+ private static final Intent sIdleMaintenanceEndIntent;
+ static {
+ sIdleMaintenanceEndIntent = new Intent(Intent.ACTION_IDLE_MAINTENANCE_END);
+ sIdleMaintenanceEndIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ }
+
+ private final AlarmManager mAlarmService;
+
+ private final BatteryService mBatteryService;
+
+ private final PendingIntent mUpdateIdleMaintenanceStatePendingIntent;
+
+ private final Context mContext;
+
+ private final WakeLock mWakeLock;
+
+ private final Handler mHandler;
+
+ private long mLastIdleMaintenanceStartTimeMillis;
+
+ private long mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID;
+
+ private boolean mIdleMaintenanceStarted;
+
+ 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, LOG_TAG);
+
+ mHandler = new Handler(mContext.getMainLooper());
+
+ 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 updateIdleMaintenanceState(boolean noisy) {
+ if (mIdleMaintenanceStarted) {
+ // Idle maintenance can be interrupted by user activity, or duration
+ // time out, or low battery.
+ if (!lastUserActivityPermitsIdleMaintenanceRunning()
+ || !batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) {
+ unscheduleUpdateIdleMaintenanceState();
+ mIdleMaintenanceStarted = false;
+ EventLogTags.writeIdleMaintenanceWindowFinish(SystemClock.elapsedRealtime(),
+ mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
+ isBatteryCharging() ? 1 : 0);
+ sendIdleMaintenanceEndIntent();
+ // 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 (!batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) {
+ scheduleUpdateIdleMaintenanceState(
+ getNextIdleMaintenanceIntervalStartFromNow());
+ }
+ }
+ } 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();
+ sendIdleMaintenanceStartIntent();
+ } 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());
+ }
+ }
+ }
+
+ private long getNextIdleMaintenanceIntervalStartFromNow() {
+ return mLastIdleMaintenanceStartTimeMillis + MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS
+ - SystemClock.elapsedRealtime();
+ }
+
+ private void sendIdleMaintenanceStartIntent() {
+ mWakeLock.acquire();
+ try {
+ ActivityManagerNative.getDefault().performIdleMaintenance();
+ } catch (RemoteException e) {
+ }
+ mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceStartIntent, UserHandle.ALL,
+ null, this, mHandler, Activity.RESULT_OK, null, null);
+ }
+
+ private void sendIdleMaintenanceEndIntent() {
+ mWakeLock.acquire();
+ mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceEndIntent, UserHandle.ALL,
+ null, this, mHandler, Activity.RESULT_OK, null, null);
+ }
+
+ 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(LOG_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) {
+ updateIdleMaintenanceState(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.
+ updateIdleMaintenanceState(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)) {
+ updateIdleMaintenanceState(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;
+ updateIdleMaintenanceState(true);
+ } else if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)
+ || Intent.ACTION_IDLE_MAINTENANCE_END.equals(action)) {
+ // We were holding a wake lock while broadcasting the idle maintenance
+ // intents but now that we finished the broadcast release the wake lock.
+ mWakeLock.release();
+ }
+ }
+}