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