1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server; 18 19 import android.app.Activity; 20 import android.app.AlarmManager; 21 import android.app.PendingIntent; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.os.Handler; 27 import android.os.Looper; 28 import android.os.PowerManager; 29 import android.os.PowerManager.WakeLock; 30 import android.os.SystemClock; 31 import android.os.UserHandle; 32 import android.util.Log; 33 34 /** 35 * This service observes the device state and when applicable sends 36 * broadcasts at the beginning and at the end of a period during which 37 * observers can perform idle maintenance tasks. Typical use of the 38 * idle maintenance is to perform somehow expensive tasks that can be 39 * postponed to a moment when they will not degrade user experience. 40 * 41 * The current implementation is very simple. The start of a maintenance 42 * window is announced if: the screen is off or showing a dream AND the 43 * battery level is more than twenty percent AND at least one hour passed 44 * activity). 45 * 46 * The end of a maintenance window is announced only if: a start was 47 * announced AND the screen turned on or a dream was stopped. 48 */ 49 public class IdleMaintenanceService extends BroadcastReceiver { 50 51 private static final boolean DEBUG = false; 52 53 private static final String LOG_TAG = IdleMaintenanceService.class.getSimpleName(); 54 55 private static final int LAST_USER_ACTIVITY_TIME_INVALID = -1; 56 57 private static final long MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS = 24 * 60 * 60 * 1000; // 1 day 58 59 private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING = 30; // percent 60 61 private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_NOT_CHARGING = 80; // percent 62 63 private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_RUNNING = 20; // percent 64 65 private static final long MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START = 71 * 60 * 1000; // 71 min 66 67 private static final long MAX_IDLE_MAINTENANCE_DURATION = 71 * 60 * 1000; // 71 min 68 69 private static final String ACTION_UPDATE_IDLE_MAINTENANCE_STATE = 70 "com.android.server.IdleMaintenanceService.action.UPDATE_IDLE_MAINTENANCE_STATE"; 71 72 private static final Intent sIdleMaintenanceStartIntent; 73 static { 74 sIdleMaintenanceStartIntent = new Intent(Intent.ACTION_IDLE_MAINTENANCE_START); 75 sIdleMaintenanceStartIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 76 }; 77 78 private static final Intent sIdleMaintenanceEndIntent; 79 static { 80 sIdleMaintenanceEndIntent = new Intent(Intent.ACTION_IDLE_MAINTENANCE_END); 81 sIdleMaintenanceEndIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 82 } 83 84 private final AlarmManager mAlarmService; 85 86 private final BatteryService mBatteryService; 87 88 private final PendingIntent mUpdateIdleMaintenanceStatePendingIntent; 89 90 private final Context mContext; 91 92 private final WakeLock mWakeLock; 93 94 private final Handler mHandler; 95 96 private long mLastIdleMaintenanceStartTimeMillis; 97 98 private long mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID; 99 100 private boolean mIdleMaintenanceStarted; 101 IdleMaintenanceService(Context context, BatteryService batteryService)102 public IdleMaintenanceService(Context context, BatteryService batteryService) { 103 mContext = context; 104 mBatteryService = batteryService; 105 106 mAlarmService = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 107 108 PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 109 mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG); 110 111 mHandler = new Handler(mContext.getMainLooper()); 112 113 Intent intent = new Intent(ACTION_UPDATE_IDLE_MAINTENANCE_STATE); 114 intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 115 mUpdateIdleMaintenanceStatePendingIntent = PendingIntent.getBroadcast(mContext, 0, 116 intent, PendingIntent.FLAG_UPDATE_CURRENT); 117 118 register(mContext.getMainLooper()); 119 } 120 register(Looper looper)121 public void register(Looper looper) { 122 IntentFilter intentFilter = new IntentFilter(); 123 124 // Alarm actions. 125 intentFilter.addAction(ACTION_UPDATE_IDLE_MAINTENANCE_STATE); 126 127 // Battery actions. 128 intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); 129 130 // Screen actions. 131 intentFilter.addAction(Intent.ACTION_SCREEN_ON); 132 intentFilter.addAction(Intent.ACTION_SCREEN_OFF); 133 134 // Dream actions. 135 intentFilter.addAction(Intent.ACTION_DREAMING_STARTED); 136 intentFilter.addAction(Intent.ACTION_DREAMING_STOPPED); 137 138 mContext.registerReceiverAsUser(this, UserHandle.ALL, 139 intentFilter, null, new Handler(looper)); 140 } 141 scheduleUpdateIdleMaintenanceState(long delayMillis)142 private void scheduleUpdateIdleMaintenanceState(long delayMillis) { 143 final long triggetRealTimeMillis = SystemClock.elapsedRealtime() + delayMillis; 144 mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggetRealTimeMillis, 145 mUpdateIdleMaintenanceStatePendingIntent); 146 } 147 unscheduleUpdateIdleMaintenanceState()148 private void unscheduleUpdateIdleMaintenanceState() { 149 mAlarmService.cancel(mUpdateIdleMaintenanceStatePendingIntent); 150 } 151 updateIdleMaintenanceState()152 private void updateIdleMaintenanceState() { 153 if (mIdleMaintenanceStarted) { 154 // Idle maintenance can be interrupted by user activity, or duration 155 // time out, or low battery. 156 if (!lastUserActivityPermitsIdleMaintenanceRunning() 157 || !batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) { 158 unscheduleUpdateIdleMaintenanceState(); 159 mIdleMaintenanceStarted = false; 160 EventLogTags.writeIdleMaintenanceWindowFinish(SystemClock.elapsedRealtime(), 161 mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(), 162 isBatteryCharging() ? 1 : 0); 163 sendIdleMaintenanceEndIntent(); 164 // We stopped since we don't have enough battery or timed out but the 165 // user is not using the device, so we should be able to run maintenance 166 // in the next maintenance window since the battery may be charged 167 // without interaction and the min interval between maintenances passed. 168 if (!batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) { 169 scheduleUpdateIdleMaintenanceState( 170 getNextIdleMaintenanceIntervalStartFromNow()); 171 } 172 } 173 } else if (deviceStatePermitsIdleMaintenanceStart() 174 && lastUserActivityPermitsIdleMaintenanceStart() 175 && lastRunPermitsIdleMaintenanceStart()) { 176 // Now that we started idle maintenance, we should schedule another 177 // update for the moment when the idle maintenance times out. 178 scheduleUpdateIdleMaintenanceState(MAX_IDLE_MAINTENANCE_DURATION); 179 mIdleMaintenanceStarted = true; 180 EventLogTags.writeIdleMaintenanceWindowStart(SystemClock.elapsedRealtime(), 181 mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(), 182 isBatteryCharging() ? 1 : 0); 183 mLastIdleMaintenanceStartTimeMillis = SystemClock.elapsedRealtime(); 184 sendIdleMaintenanceStartIntent(); 185 } else if (lastUserActivityPermitsIdleMaintenanceStart()) { 186 if (lastRunPermitsIdleMaintenanceStart()) { 187 // The user does not use the device and we did not run maintenance in more 188 // than the min interval between runs, so schedule an update - maybe the 189 // battery will be charged latter. 190 scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START); 191 } else { 192 // The user does not use the device but we have run maintenance in the min 193 // interval between runs, so schedule an update after the min interval ends. 194 scheduleUpdateIdleMaintenanceState( 195 getNextIdleMaintenanceIntervalStartFromNow()); 196 } 197 } 198 } 199 getNextIdleMaintenanceIntervalStartFromNow()200 private long getNextIdleMaintenanceIntervalStartFromNow() { 201 return mLastIdleMaintenanceStartTimeMillis + MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS 202 - SystemClock.elapsedRealtime(); 203 } 204 sendIdleMaintenanceStartIntent()205 private void sendIdleMaintenanceStartIntent() { 206 mWakeLock.acquire(); 207 mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceStartIntent, UserHandle.ALL, 208 null, this, mHandler, Activity.RESULT_OK, null, null); 209 } 210 sendIdleMaintenanceEndIntent()211 private void sendIdleMaintenanceEndIntent() { 212 mWakeLock.acquire(); 213 mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceEndIntent, UserHandle.ALL, 214 null, this, mHandler, Activity.RESULT_OK, null, null); 215 } 216 deviceStatePermitsIdleMaintenanceStart()217 private boolean deviceStatePermitsIdleMaintenanceStart() { 218 final int minBatteryLevel = isBatteryCharging() 219 ? MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING 220 : MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_NOT_CHARGING; 221 return (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID 222 && mBatteryService.getBatteryLevel() > minBatteryLevel); 223 } 224 lastUserActivityPermitsIdleMaintenanceStart()225 private boolean lastUserActivityPermitsIdleMaintenanceStart() { 226 // The last time the user poked the device is above the threshold. 227 return (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID 228 && SystemClock.elapsedRealtime() - mLastUserActivityElapsedTimeMillis 229 > MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START); 230 } 231 lastRunPermitsIdleMaintenanceStart()232 private boolean lastRunPermitsIdleMaintenanceStart() { 233 // Enough time passed since the last maintenance run. 234 return SystemClock.elapsedRealtime() - mLastIdleMaintenanceStartTimeMillis 235 > MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS; 236 } 237 lastUserActivityPermitsIdleMaintenanceRunning()238 private boolean lastUserActivityPermitsIdleMaintenanceRunning() { 239 // The user is not using the device. 240 return (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID); 241 } 242 batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()243 private boolean batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning() { 244 // Battery not too low and the maintenance duration did not timeout. 245 return (mBatteryService.getBatteryLevel() > MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_RUNNING 246 && mLastIdleMaintenanceStartTimeMillis + MAX_IDLE_MAINTENANCE_DURATION 247 > SystemClock.elapsedRealtime()); 248 } 249 isBatteryCharging()250 private boolean isBatteryCharging() { 251 return mBatteryService.getPlugType() > 0 252 && mBatteryService.getInvalidCharger() == 0; 253 } 254 255 @Override onReceive(Context context, Intent intent)256 public void onReceive(Context context, Intent intent) { 257 if (DEBUG) { 258 Log.i(LOG_TAG, intent.getAction()); 259 } 260 String action = intent.getAction(); 261 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { 262 // We care about battery only if maintenance is in progress so we can 263 // stop it if battery is too low. Note that here we assume that the 264 // maintenance clients are properly holding a wake lock. We will 265 // refactor the maintenance to use services instead of intents for the 266 // next release. The only client for this for now is internal an holds 267 // a wake lock correctly. 268 if (mIdleMaintenanceStarted) { 269 updateIdleMaintenanceState(); 270 } 271 } else if (Intent.ACTION_SCREEN_ON.equals(action) 272 || Intent.ACTION_DREAMING_STOPPED.equals(action)) { 273 mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID; 274 // Unschedule any future updates since we already know that maintenance 275 // cannot be performed since the user is back. 276 unscheduleUpdateIdleMaintenanceState(); 277 // If the screen went on/stopped dreaming, we know the user is using the 278 // device which means that idle maintenance should be stopped if running. 279 updateIdleMaintenanceState(); 280 } else if (Intent.ACTION_SCREEN_OFF.equals(action) 281 || Intent.ACTION_DREAMING_STARTED.equals(action)) { 282 mLastUserActivityElapsedTimeMillis = SystemClock.elapsedRealtime(); 283 // If screen went off/started dreaming, we may be able to start idle maintenance 284 // after the minimal user inactivity elapses. We schedule an alarm for when 285 // this timeout elapses since the device may go to sleep by then. 286 scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START); 287 } else if (ACTION_UPDATE_IDLE_MAINTENANCE_STATE.equals(action)) { 288 updateIdleMaintenanceState(); 289 } else if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action) 290 || Intent.ACTION_IDLE_MAINTENANCE_END.equals(action)) { 291 // We were holding a wake lock while broadcasting the idle maintenance 292 // intents but now that we finished the broadcast release the wake lock. 293 mWakeLock.release(); 294 } 295 } 296 } 297