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