Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 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 | |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 19 | import android.app.AlarmManager; |
| 20 | import android.app.PendingIntent; |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 21 | import android.app.maintenance.IIdleCallback; |
| 22 | import android.app.maintenance.IIdleService; |
| 23 | import android.app.maintenance.IdleService; |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 24 | import android.content.BroadcastReceiver; |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 25 | import android.content.ComponentName; |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 26 | import android.content.Context; |
| 27 | import android.content.Intent; |
| 28 | import android.content.IntentFilter; |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 29 | import android.content.ServiceConnection; |
| 30 | import android.content.pm.PackageManager; |
| 31 | import android.content.pm.ResolveInfo; |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 32 | import android.os.Handler; |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 33 | import android.os.PowerManager; |
| 34 | import android.os.PowerManager.WakeLock; |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 35 | import android.os.IBinder; |
| 36 | import android.os.Looper; |
| 37 | import android.os.Message; |
| 38 | import android.os.Process; |
Dianne Hackborn | 35f72be | 2013-09-16 10:57:39 -0700 | [diff] [blame] | 39 | import android.os.RemoteException; |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 40 | import android.os.SystemClock; |
| 41 | import android.os.UserHandle; |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 42 | import android.os.WorkSource; |
| 43 | import android.util.ArrayMap; |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 44 | import android.util.Log; |
Dianne Hackborn | 35f72be | 2013-09-16 10:57:39 -0700 | [diff] [blame] | 45 | import android.util.Slog; |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 46 | import android.util.SparseArray; |
| 47 | |
| 48 | import java.util.LinkedList; |
| 49 | import java.util.List; |
| 50 | import java.util.Random; |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 51 | |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 52 | /** |
| 53 | * This service observes the device state and when applicable sends |
| 54 | * broadcasts at the beginning and at the end of a period during which |
| 55 | * observers can perform idle maintenance tasks. Typical use of the |
| 56 | * idle maintenance is to perform somehow expensive tasks that can be |
| 57 | * postponed to a moment when they will not degrade user experience. |
| 58 | * |
| 59 | * The current implementation is very simple. The start of a maintenance |
| 60 | * window is announced if: the screen is off or showing a dream AND the |
| 61 | * battery level is more than twenty percent AND at least one hour passed |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 62 | * activity). |
| 63 | * |
| 64 | * The end of a maintenance window is announced only if: a start was |
| 65 | * announced AND the screen turned on or a dream was stopped. |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 66 | * |
| 67 | * Method naming note: |
| 68 | * Methods whose name ends with "Tm" must only be called from the main thread. |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 69 | */ |
| 70 | public class IdleMaintenanceService extends BroadcastReceiver { |
| 71 | |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 72 | private static final boolean DEBUG = false; |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 73 | |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 74 | private static final String TAG = IdleMaintenanceService.class.getSimpleName(); |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 75 | |
| 76 | private static final int LAST_USER_ACTIVITY_TIME_INVALID = -1; |
| 77 | |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 78 | private static final long MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS = 24 * 60 * 60 * 1000; // 1 day |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 79 | |
Svetoslav | f23b64d | 2013-04-25 14:45:54 -0700 | [diff] [blame] | 80 | private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING = 30; // percent |
| 81 | |
| 82 | private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_NOT_CHARGING = 80; // percent |
| 83 | |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 84 | private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_RUNNING = 20; // percent |
Svetoslav | f23b64d | 2013-04-25 14:45:54 -0700 | [diff] [blame] | 85 | |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 86 | private static final long MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START = 71 * 60 * 1000; // 71 min |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 87 | |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 88 | private static final long MAX_IDLE_MAINTENANCE_DURATION = 71 * 60 * 1000; // 71 min |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 89 | |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 90 | private static final String ACTION_UPDATE_IDLE_MAINTENANCE_STATE = |
| 91 | "com.android.server.IdleMaintenanceService.action.UPDATE_IDLE_MAINTENANCE_STATE"; |
| 92 | |
Dianne Hackborn | 35f72be | 2013-09-16 10:57:39 -0700 | [diff] [blame] | 93 | private static final String ACTION_FORCE_IDLE_MAINTENANCE = |
| 94 | "com.android.server.IdleMaintenanceService.action.FORCE_IDLE_MAINTENANCE"; |
| 95 | |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 96 | static final int MSG_OP_COMPLETE = 1; |
| 97 | static final int MSG_IDLE_FINISHED = 2; |
| 98 | static final int MSG_TIMEOUT = 3; |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 99 | |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 100 | // when a timeout happened, what were we expecting? |
| 101 | static final int VERB_BINDING = 1; |
| 102 | static final int VERB_IDLING = 2; |
| 103 | static final int VERB_ENDING = 3; |
| 104 | |
| 105 | // What are our relevant timeouts / allocated slices? |
| 106 | static final long OP_TIMEOUT = 8 * 1000; // 8 seconds to bind or ack the start |
| 107 | static final long IDLE_TIMESLICE = 10 * 60 * 1000; // ten minutes for each idler |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 108 | |
| 109 | private final AlarmManager mAlarmService; |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 110 | private final BatteryService mBatteryService; |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 111 | private final PendingIntent mUpdateIdleMaintenanceStatePendingIntent; |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 112 | private final Context mContext; |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 113 | private final WakeLock mWakeLock; |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 114 | private final WorkSource mSystemWorkSource = new WorkSource(Process.myUid()); |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 115 | |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 116 | private long mLastIdleMaintenanceStartTimeMillis; |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 117 | private long mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID; |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 118 | private boolean mIdleMaintenanceStarted; |
| 119 | |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 120 | final IdleCallback mCallback; |
| 121 | final Handler mHandler; |
| 122 | |
| 123 | final Random mTokenGenerator = new Random(); |
| 124 | |
| 125 | int makeToken() { |
| 126 | int token; |
| 127 | do { |
| 128 | token = mTokenGenerator.nextInt(Integer.MAX_VALUE); |
| 129 | } while (token == 0); |
| 130 | return token; |
| 131 | } |
| 132 | |
| 133 | class ActiveTask { |
| 134 | public IdleServiceInfo who; |
| 135 | public int verb; |
| 136 | public int token; |
| 137 | |
| 138 | ActiveTask(IdleServiceInfo target, int action) { |
| 139 | who = target; |
| 140 | verb = action; |
| 141 | token = makeToken(); |
| 142 | } |
| 143 | |
| 144 | @Override |
| 145 | public String toString() { |
| 146 | return "ActiveTask{" + Integer.toHexString(this.hashCode()) |
| 147 | + " : verb=" + verb |
| 148 | + " : token=" + token |
| 149 | + " : "+ who + "}"; |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | // What operations are in flight? |
| 154 | final SparseArray<ActiveTask> mPendingOperations = new SparseArray<ActiveTask>(); |
| 155 | |
| 156 | // Idle service queue management |
| 157 | class IdleServiceInfo { |
| 158 | public final ComponentName componentName; |
| 159 | public final int uid; |
| 160 | public IIdleService service; |
| 161 | |
| 162 | IdleServiceInfo(ResolveInfo info, ComponentName cname) { |
| 163 | componentName = cname; // derived from 'info' but this avoids an extra object |
| 164 | uid = info.serviceInfo.applicationInfo.uid; |
| 165 | service = null; |
| 166 | } |
| 167 | |
| 168 | @Override |
| 169 | public int hashCode() { |
| 170 | return componentName.hashCode(); |
| 171 | } |
| 172 | |
| 173 | @Override |
| 174 | public String toString() { |
| 175 | return "IdleServiceInfo{" + componentName |
| 176 | + " / " + (service == null ? "null" : service.asBinder()) + "}"; |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | final ArrayMap<ComponentName, IdleServiceInfo> mIdleServices = |
| 181 | new ArrayMap<ComponentName, IdleServiceInfo>(); |
| 182 | final LinkedList<IdleServiceInfo> mIdleServiceQueue = new LinkedList<IdleServiceInfo>(); |
| 183 | IdleServiceInfo mCurrentIdler; // set when we've committed to launching an idler |
| 184 | IdleServiceInfo mLastIdler; // end of queue when idling begins |
| 185 | |
| 186 | void reportNoTimeout(int token, boolean result) { |
| 187 | final Message msg = mHandler.obtainMessage(MSG_OP_COMPLETE, result ? 1 : 0, token); |
| 188 | mHandler.sendMessage(msg); |
| 189 | } |
| 190 | |
| 191 | // Binder acknowledgment trampoline |
| 192 | class IdleCallback extends IIdleCallback.Stub { |
| 193 | @Override |
| 194 | public void acknowledgeStart(int token, boolean result) throws RemoteException { |
| 195 | reportNoTimeout(token, result); |
| 196 | } |
| 197 | |
| 198 | @Override |
| 199 | public void acknowledgeStop(int token) throws RemoteException { |
| 200 | reportNoTimeout(token, false); |
| 201 | } |
| 202 | |
| 203 | @Override |
| 204 | public void idleFinished(int token) throws RemoteException { |
| 205 | if (DEBUG) { |
| 206 | Slog.v(TAG, "idleFinished: " + token); |
| 207 | } |
| 208 | final Message msg = mHandler.obtainMessage(MSG_IDLE_FINISHED, 0, token); |
| 209 | mHandler.sendMessage(msg); |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | // Stuff that we run on a Handler |
| 214 | class IdleHandler extends Handler { |
| 215 | public IdleHandler(Looper looper) { |
| 216 | super(looper); |
| 217 | } |
| 218 | |
| 219 | @Override |
| 220 | public void handleMessage(Message msg) { |
| 221 | final int token = msg.arg2; |
| 222 | |
| 223 | switch (msg.what) { |
| 224 | case MSG_OP_COMPLETE: { |
| 225 | if (DEBUG) { |
| 226 | Slog.i(TAG, "MSG_OP_COMPLETE of " + token); |
| 227 | } |
| 228 | ActiveTask task = mPendingOperations.get(token); |
| 229 | if (task != null) { |
| 230 | mPendingOperations.remove(token); |
| 231 | removeMessages(MSG_TIMEOUT); |
| 232 | |
| 233 | handleOpCompleteTm(task, msg.arg1); |
| 234 | } else { |
| 235 | // Can happen in a race between timeout and actual |
| 236 | // (belated) completion of a "begin idling" or similar |
| 237 | // operation. In that state we've already processed the |
| 238 | // timeout, so we intentionally no-op here. |
| 239 | if (DEBUG) { |
| 240 | Slog.w(TAG, "Belated op-complete of " + token); |
| 241 | } |
| 242 | } |
| 243 | break; |
| 244 | } |
| 245 | |
| 246 | case MSG_IDLE_FINISHED: { |
| 247 | if (DEBUG) { |
| 248 | Slog.i(TAG, "MSG_IDLE_FINISHED of " + token); |
| 249 | } |
| 250 | ActiveTask task = mPendingOperations.get(token); |
| 251 | if (task != null) { |
| 252 | if (DEBUG) { |
| 253 | Slog.i(TAG, "... removing task " + token); |
| 254 | } |
| 255 | mPendingOperations.remove(token); |
| 256 | removeMessages(MSG_TIMEOUT); |
| 257 | |
| 258 | handleIdleFinishedTm(task); |
| 259 | } else { |
| 260 | // Can happen "legitimately" from an app explicitly calling |
| 261 | // idleFinished() after already having been told that its slice |
| 262 | // has ended. |
| 263 | if (DEBUG) { |
| 264 | Slog.w(TAG, "Belated idle-finished of " + token); |
| 265 | } |
| 266 | } |
| 267 | break; |
| 268 | } |
| 269 | |
| 270 | case MSG_TIMEOUT: { |
| 271 | if (DEBUG) { |
| 272 | Slog.i(TAG, "MSG_TIMEOUT of " + token); |
| 273 | } |
| 274 | ActiveTask task = mPendingOperations.get(token); |
| 275 | if (task != null) { |
| 276 | mPendingOperations.remove(token); |
| 277 | removeMessages(MSG_OP_COMPLETE); |
| 278 | |
| 279 | handleTimeoutTm(task); |
| 280 | } else { |
| 281 | // This one should not happen; we flushed timeout messages |
| 282 | // whenever we entered a state after which we have established |
| 283 | // that they are not appropriate. |
| 284 | Slog.w(TAG, "Unexpected timeout of " + token); |
| 285 | } |
| 286 | break; |
| 287 | } |
| 288 | |
| 289 | default: |
| 290 | Slog.w(TAG, "Unknown message: " + msg.what); |
| 291 | } |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | void handleTimeoutTm(ActiveTask task) { |
| 296 | switch (task.verb) { |
| 297 | case VERB_BINDING: { |
| 298 | // We were trying to bind to this service, but it wedged or otherwise |
| 299 | // failed to respond in time. Let it stay in the queue for the next |
| 300 | // time around, but just give up on it for now and go on to the next. |
| 301 | startNextIdleServiceTm(); |
| 302 | break; |
| 303 | } |
| 304 | case VERB_IDLING: { |
| 305 | // The service has reached the end of its designated idle timeslice. |
| 306 | // This is not considered an error. |
| 307 | if (DEBUG) { |
| 308 | Slog.i(TAG, "Idler reached end of timeslice: " + task.who); |
| 309 | } |
| 310 | sendEndIdleTm(task.who); |
| 311 | break; |
| 312 | } |
| 313 | case VERB_ENDING: { |
| 314 | if (mCurrentIdler == task.who) { |
| 315 | if (DEBUG) { |
| 316 | Slog.i(TAG, "Task timed out when ending; unbind needed"); |
| 317 | } |
| 318 | handleIdleFinishedTm(task); |
| 319 | } else { |
| 320 | if (DEBUG) { |
| 321 | Slog.w(TAG, "Ending timeout for non-current idle service!"); |
| 322 | } |
| 323 | } |
| 324 | break; |
| 325 | } |
| 326 | default: { |
| 327 | Slog.w(TAG, "Unknown timeout state " + task.verb); |
| 328 | break; |
| 329 | } |
| 330 | } |
| 331 | } |
| 332 | |
| 333 | void handleOpCompleteTm(ActiveTask task, int result) { |
| 334 | if (DEBUG) { |
| 335 | Slog.i(TAG, "handleOpComplete : task=" + task + " result=" + result); |
| 336 | } |
| 337 | if (task.verb == VERB_IDLING) { |
| 338 | // If the service was told to begin idling and responded positively, then |
| 339 | // it has begun idling and will eventually either explicitly finish, or |
| 340 | // reach the end of its allotted timeslice. It's running free now, so we |
| 341 | // just schedule the idle-expiration timeout under the token it's already been |
| 342 | // given and let it keep going. |
| 343 | if (result != 0) { |
| 344 | scheduleOpTimeoutTm(task); |
| 345 | } else { |
| 346 | // The idle service has indicated that it does not, in fact, |
| 347 | // need to run at present, so we immediately indicate that it's |
| 348 | // to finish idling, and go on to the next idler. |
| 349 | if (DEBUG) { |
| 350 | Slog.i(TAG, "Idler declined idling; moving along"); |
| 351 | } |
| 352 | sendEndIdleTm(task.who); |
| 353 | } |
| 354 | } else { |
| 355 | // In the idling case, the task will be cleared either as the result of a timeout |
| 356 | // or of an explicit idleFinished(). For all other operations (binding, ending) we |
| 357 | // are done with the task as such, so we remove it from our bookkeeping. |
| 358 | if (DEBUG) { |
| 359 | Slog.i(TAG, "Clearing task " + task); |
| 360 | } |
| 361 | mPendingOperations.remove(task.token); |
| 362 | if (task.verb == VERB_ENDING) { |
| 363 | // The last bit of handshaking around idle cessation for this target |
| 364 | handleIdleFinishedTm(task); |
| 365 | } |
| 366 | } |
| 367 | } |
| 368 | |
| 369 | void handleIdleFinishedTm(ActiveTask task) { |
| 370 | final IdleServiceInfo who = task.who; |
| 371 | if (who == mCurrentIdler) { |
| 372 | if (DEBUG) { |
| 373 | Slog.i(TAG, "Current idler has finished: " + who); |
| 374 | Slog.i(TAG, "Attributing wakelock to system work source"); |
| 375 | } |
| 376 | mContext.unbindService(mConnection); |
| 377 | startNextIdleServiceTm(); |
| 378 | } else { |
| 379 | Slog.w(TAG, "finish from non-current idle service? " + who); |
| 380 | } |
| 381 | } |
| 382 | |
| 383 | void updateIdleServiceQueueTm() { |
| 384 | if (DEBUG) { |
| 385 | Slog.i(TAG, "Updating idle service queue"); |
| 386 | } |
| 387 | PackageManager pm = mContext.getPackageManager(); |
| 388 | Intent idleIntent = new Intent(IdleService.SERVICE_INTERFACE); |
| 389 | List<ResolveInfo> services = pm.queryIntentServices(idleIntent, 0); |
| 390 | for (ResolveInfo info : services) { |
| 391 | if (info.serviceInfo != null) { |
| 392 | if (IdleService.PERMISSION_BIND.equals(info.serviceInfo.permission)) { |
| 393 | final ComponentName componentName = new ComponentName( |
| 394 | info.serviceInfo.packageName, |
| 395 | info.serviceInfo.name); |
| 396 | if (DEBUG) { |
| 397 | Slog.i(TAG, " - " + componentName); |
| 398 | } |
| 399 | if (!mIdleServices.containsKey(componentName)) { |
| 400 | if (DEBUG) { |
| 401 | Slog.i(TAG, " + not known; adding"); |
| 402 | } |
| 403 | IdleServiceInfo serviceInfo = new IdleServiceInfo(info, componentName); |
| 404 | mIdleServices.put(componentName, serviceInfo); |
| 405 | mIdleServiceQueue.add(serviceInfo); |
| 406 | } |
| 407 | } else { |
| 408 | if (DEBUG) { |
| 409 | Slog.i(TAG, "Idle service " + info.serviceInfo |
| 410 | + " does not have required permission; ignoring"); |
| 411 | } |
| 412 | } |
| 413 | } |
| 414 | } |
| 415 | } |
| 416 | |
| 417 | void startNextIdleServiceTm() { |
| 418 | mWakeLock.setWorkSource(mSystemWorkSource); |
| 419 | |
| 420 | if (mLastIdler == null) { |
| 421 | // we've run the queue; nothing more to do until the next idle interval. |
| 422 | if (DEBUG) { |
| 423 | Slog.i(TAG, "Queue already drained; nothing more to do"); |
| 424 | } |
| 425 | return; |
| 426 | } |
| 427 | |
| 428 | if (DEBUG) { |
| 429 | Slog.i(TAG, "startNextIdleService : last=" + mLastIdler + " cur=" + mCurrentIdler); |
| 430 | if (mIdleServiceQueue.size() > 0) { |
| 431 | int i = 0; |
| 432 | Slog.i(TAG, "Queue (" + mIdleServiceQueue.size() + "):"); |
| 433 | for (IdleServiceInfo info : mIdleServiceQueue) { |
| 434 | Slog.i(TAG, " " + i + " : " + info); |
| 435 | i++; |
| 436 | } |
| 437 | } |
| 438 | } |
| 439 | if (mCurrentIdler != mLastIdler) { |
| 440 | if (mIdleServiceQueue.size() > 0) { |
| 441 | IdleServiceInfo target = mIdleServiceQueue.pop(); |
| 442 | if (DEBUG) { |
| 443 | Slog.i(TAG, "starting next idle service " + target); |
| 444 | } |
| 445 | Intent idleIntent = new Intent(IdleService.SERVICE_INTERFACE); |
| 446 | idleIntent.setComponent(target.componentName); |
| 447 | mCurrentIdler = target; |
| 448 | ActiveTask task = new ActiveTask(target, VERB_BINDING); |
| 449 | scheduleOpTimeoutTm(task); |
| 450 | boolean bindOk = mContext.bindServiceAsUser(idleIntent, mConnection, |
| 451 | Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY, UserHandle.OWNER); |
| 452 | if (!bindOk) { |
| 453 | if (DEBUG) { |
| 454 | Slog.w(TAG, "bindService() to " + target.componentName |
| 455 | + " failed"); |
| 456 | } |
| 457 | } else { |
| 458 | mIdleServiceQueue.add(target); // at the end for next time |
| 459 | if (DEBUG) { Slog.i(TAG, "Attributing wakelock to target uid " + target.uid); } |
| 460 | mWakeLock.setWorkSource(new WorkSource(target.uid)); |
| 461 | } |
| 462 | } else { |
| 463 | // Queue is empty but mLastIdler is non-null -- eeep. Clear *everything* |
| 464 | // and wind up until the next time around. |
| 465 | Slog.e(TAG, "Queue unexpectedly empty; resetting. last=" |
| 466 | + mLastIdler + " cur=" + mCurrentIdler); |
| 467 | mHandler.removeMessages(MSG_TIMEOUT); |
| 468 | mPendingOperations.clear(); |
| 469 | stopIdleMaintenanceTm(); |
| 470 | } |
| 471 | } else { |
| 472 | // we've reached the place we started, so mark the queue as drained |
| 473 | if (DEBUG) { |
| 474 | Slog.i(TAG, "Reached end of queue."); |
| 475 | } |
| 476 | stopIdleMaintenanceTm(); |
| 477 | } |
| 478 | } |
| 479 | |
| 480 | void sendStartIdleTm(IdleServiceInfo who) { |
| 481 | ActiveTask task = new ActiveTask(who, VERB_IDLING); |
| 482 | scheduleOpTimeoutTm(task); |
| 483 | try { |
| 484 | who.service.startIdleMaintenance(mCallback, task.token); |
| 485 | } catch (RemoteException e) { |
| 486 | // We bound to it, but now we can't reach it. Bail and go on to the |
| 487 | // next service. |
| 488 | mContext.unbindService(mConnection); |
| 489 | if (DEBUG) { Slog.i(TAG, "Attributing wakelock to system work source"); } |
| 490 | mHandler.removeMessages(MSG_TIMEOUT); |
| 491 | startNextIdleServiceTm(); |
| 492 | } |
| 493 | } |
| 494 | |
| 495 | void sendEndIdleTm(IdleServiceInfo who) { |
| 496 | ActiveTask task = new ActiveTask(who, VERB_ENDING); |
| 497 | scheduleOpTimeoutTm(task); |
| 498 | if (DEBUG) { |
| 499 | Slog.i(TAG, "Sending end-idle to " + who); |
| 500 | } |
| 501 | try { |
| 502 | who.service.stopIdleMaintenance(mCallback, task.token); |
| 503 | } catch (RemoteException e) { |
| 504 | // We bound to it, but now we can't reach it. Bail and go on to the |
| 505 | // next service. |
| 506 | mContext.unbindService(mConnection); |
| 507 | if (DEBUG) { Slog.i(TAG, "Attributing wakelock to system work source"); } |
| 508 | mHandler.removeMessages(MSG_TIMEOUT); |
| 509 | startNextIdleServiceTm(); |
| 510 | } |
| 511 | } |
| 512 | |
| 513 | ServiceConnection mConnection = new ServiceConnection() { |
| 514 | @Override |
| 515 | public void onServiceConnected(ComponentName name, IBinder service) { |
| 516 | if (DEBUG) { |
| 517 | Slog.i(TAG, "onServiceConnected(" + name + ")"); |
| 518 | } |
| 519 | IdleServiceInfo info = mIdleServices.get(name); |
| 520 | if (info != null) { |
| 521 | // Bound! Cancel the bind timeout |
| 522 | mHandler.removeMessages(MSG_TIMEOUT); |
| 523 | // Now tell it to start its idle work |
| 524 | info.service = IIdleService.Stub.asInterface(service); |
| 525 | sendStartIdleTm(info); |
| 526 | } else { |
| 527 | // We bound to a service we don't know about. That's ungood. |
| 528 | Slog.e(TAG, "Connected to unexpected component " + name); |
| 529 | mContext.unbindService(this); |
| 530 | } |
| 531 | } |
| 532 | |
| 533 | @Override |
| 534 | public void onServiceDisconnected(ComponentName name) { |
| 535 | if (DEBUG) { |
| 536 | Slog.i(TAG, "onServiceDisconnected(" + name + ")"); |
| 537 | } |
| 538 | IdleServiceInfo who = mIdleServices.get(name); |
| 539 | if (who == mCurrentIdler) { |
| 540 | // Hm, okay; they didn't tell us they were finished but they |
| 541 | // went away. Crashed, probably. Oh well. They're gone, so |
| 542 | // we can't finish them cleanly; just force things along. |
| 543 | Slog.w(TAG, "Idler unexpectedly vanished: " + mCurrentIdler); |
| 544 | mContext.unbindService(this); |
| 545 | mHandler.removeMessages(MSG_TIMEOUT); |
| 546 | startNextIdleServiceTm(); |
| 547 | } else { |
| 548 | // Not the current idler, so we don't interrupt our process... |
| 549 | if (DEBUG) { |
| 550 | Slog.w(TAG, "Disconnect of abandoned or unexpected service " + name); |
| 551 | } |
| 552 | } |
| 553 | } |
| 554 | }; |
| 555 | |
| 556 | // Schedules a timeout / end-of-work based on the task verb |
| 557 | void scheduleOpTimeoutTm(ActiveTask task) { |
| 558 | final long timeoutMillis = (task.verb == VERB_IDLING) ? IDLE_TIMESLICE : OP_TIMEOUT; |
| 559 | if (DEBUG) { |
| 560 | Slog.i(TAG, "Scheduling timeout (token " + task.token |
| 561 | + " : verb " + task.verb + ") for " + task + " in " + timeoutMillis); |
| 562 | } |
| 563 | mPendingOperations.put(task.token, task); |
| 564 | mHandler.removeMessages(MSG_TIMEOUT); |
| 565 | final Message msg = mHandler.obtainMessage(MSG_TIMEOUT, 0, task.token); |
| 566 | mHandler.sendMessageDelayed(msg, timeoutMillis); |
| 567 | } |
| 568 | |
| 569 | // ------------------------------------------------------------------------------- |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 570 | public IdleMaintenanceService(Context context, BatteryService batteryService) { |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 571 | mContext = context; |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 572 | mBatteryService = batteryService; |
| 573 | |
| 574 | mAlarmService = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 575 | |
| 576 | PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 577 | mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 578 | |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 579 | mHandler = new IdleHandler(mContext.getMainLooper()); |
| 580 | mCallback = new IdleCallback(); |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 581 | |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 582 | Intent intent = new Intent(ACTION_UPDATE_IDLE_MAINTENANCE_STATE); |
| 583 | intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); |
| 584 | mUpdateIdleMaintenanceStatePendingIntent = PendingIntent.getBroadcast(mContext, 0, |
| 585 | intent, PendingIntent.FLAG_UPDATE_CURRENT); |
| 586 | |
Dianne Hackborn | 35f72be | 2013-09-16 10:57:39 -0700 | [diff] [blame] | 587 | register(mHandler); |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 588 | } |
| 589 | |
Dianne Hackborn | 35f72be | 2013-09-16 10:57:39 -0700 | [diff] [blame] | 590 | public void register(Handler handler) { |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 591 | IntentFilter intentFilter = new IntentFilter(); |
| 592 | |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 593 | // Alarm actions. |
| 594 | intentFilter.addAction(ACTION_UPDATE_IDLE_MAINTENANCE_STATE); |
| 595 | |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 596 | // Battery actions. |
| 597 | intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); |
| 598 | |
| 599 | // Screen actions. |
| 600 | intentFilter.addAction(Intent.ACTION_SCREEN_ON); |
| 601 | intentFilter.addAction(Intent.ACTION_SCREEN_OFF); |
| 602 | |
| 603 | // Dream actions. |
| 604 | intentFilter.addAction(Intent.ACTION_DREAMING_STARTED); |
| 605 | intentFilter.addAction(Intent.ACTION_DREAMING_STOPPED); |
| 606 | |
| 607 | mContext.registerReceiverAsUser(this, UserHandle.ALL, |
Dianne Hackborn | 35f72be | 2013-09-16 10:57:39 -0700 | [diff] [blame] | 608 | intentFilter, null, mHandler); |
| 609 | |
| 610 | intentFilter = new IntentFilter(); |
| 611 | intentFilter.addAction(ACTION_FORCE_IDLE_MAINTENANCE); |
| 612 | mContext.registerReceiverAsUser(this, UserHandle.ALL, |
| 613 | intentFilter, android.Manifest.permission.SET_ACTIVITY_WATCHER, mHandler); |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 614 | } |
| 615 | |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 616 | private void scheduleUpdateIdleMaintenanceState(long delayMillis) { |
| 617 | final long triggetRealTimeMillis = SystemClock.elapsedRealtime() + delayMillis; |
| 618 | mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggetRealTimeMillis, |
| 619 | mUpdateIdleMaintenanceStatePendingIntent); |
| 620 | } |
| 621 | |
| 622 | private void unscheduleUpdateIdleMaintenanceState() { |
| 623 | mAlarmService.cancel(mUpdateIdleMaintenanceStatePendingIntent); |
| 624 | } |
| 625 | |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 626 | private void updateIdleMaintenanceStateTm(boolean noisy) { |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 627 | if (mIdleMaintenanceStarted) { |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 628 | // Idle maintenance can be interrupted by user activity, or duration |
| 629 | // time out, or low battery. |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 630 | final boolean batteryOk |
| 631 | = batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning(); |
| 632 | if (!lastUserActivityPermitsIdleMaintenanceRunning() || !batteryOk) { |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 633 | unscheduleUpdateIdleMaintenanceState(); |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 634 | mIdleMaintenanceStarted = false; |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 635 | // We stopped since we don't have enough battery or timed out but the |
| 636 | // user is not using the device, so we should be able to run maintenance |
| 637 | // in the next maintenance window since the battery may be charged |
| 638 | // without interaction and the min interval between maintenances passed. |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 639 | if (!batteryOk) { |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 640 | scheduleUpdateIdleMaintenanceState( |
| 641 | getNextIdleMaintenanceIntervalStartFromNow()); |
| 642 | } |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 643 | |
| 644 | EventLogTags.writeIdleMaintenanceWindowFinish(SystemClock.elapsedRealtime(), |
| 645 | mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(), |
| 646 | isBatteryCharging() ? 1 : 0); |
| 647 | scheduleIdleFinishTm(); |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 648 | } |
Dianne Hackborn | 35f72be | 2013-09-16 10:57:39 -0700 | [diff] [blame] | 649 | } else if (deviceStatePermitsIdleMaintenanceStart(noisy) |
| 650 | && lastUserActivityPermitsIdleMaintenanceStart(noisy) |
| 651 | && lastRunPermitsIdleMaintenanceStart(noisy)) { |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 652 | // Now that we started idle maintenance, we should schedule another |
| 653 | // update for the moment when the idle maintenance times out. |
| 654 | scheduleUpdateIdleMaintenanceState(MAX_IDLE_MAINTENANCE_DURATION); |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 655 | mIdleMaintenanceStarted = true; |
Svetoslav | f23b64d | 2013-04-25 14:45:54 -0700 | [diff] [blame] | 656 | EventLogTags.writeIdleMaintenanceWindowStart(SystemClock.elapsedRealtime(), |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 657 | mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(), |
| 658 | isBatteryCharging() ? 1 : 0); |
Svetoslav | f23b64d | 2013-04-25 14:45:54 -0700 | [diff] [blame] | 659 | mLastIdleMaintenanceStartTimeMillis = SystemClock.elapsedRealtime(); |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 660 | startIdleMaintenanceTm(); |
Dianne Hackborn | 35f72be | 2013-09-16 10:57:39 -0700 | [diff] [blame] | 661 | } else if (lastUserActivityPermitsIdleMaintenanceStart(noisy)) { |
| 662 | if (lastRunPermitsIdleMaintenanceStart(noisy)) { |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 663 | // The user does not use the device and we did not run maintenance in more |
| 664 | // than the min interval between runs, so schedule an update - maybe the |
| 665 | // battery will be charged latter. |
| 666 | scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START); |
| 667 | } else { |
| 668 | // The user does not use the device but we have run maintenance in the min |
| 669 | // interval between runs, so schedule an update after the min interval ends. |
| 670 | scheduleUpdateIdleMaintenanceState( |
| 671 | getNextIdleMaintenanceIntervalStartFromNow()); |
| 672 | } |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 673 | } |
| 674 | } |
| 675 | |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 676 | void startIdleMaintenanceTm() { |
| 677 | if (DEBUG) { |
| 678 | Slog.i(TAG, "*** Starting idle maintenance ***"); |
| 679 | } |
| 680 | if (DEBUG) { Slog.i(TAG, "Attributing wakelock to system work source"); } |
| 681 | mWakeLock.setWorkSource(mSystemWorkSource); |
| 682 | mWakeLock.acquire(); |
| 683 | updateIdleServiceQueueTm(); |
| 684 | mCurrentIdler = null; |
| 685 | mLastIdler = (mIdleServiceQueue.size() > 0) ? mIdleServiceQueue.peekLast() : null; |
| 686 | startNextIdleServiceTm(); |
| 687 | } |
| 688 | |
| 689 | // Start a graceful wind-down of the idle maintenance state: end the current idler |
| 690 | // and pretend that we've finished running the queue. If there's no current idler, |
| 691 | // this is a no-op. |
| 692 | void scheduleIdleFinishTm() { |
| 693 | if (mCurrentIdler != null) { |
| 694 | if (DEBUG) { |
| 695 | Slog.i(TAG, "*** Finishing idle maintenance ***"); |
| 696 | } |
| 697 | mLastIdler = mCurrentIdler; |
| 698 | sendEndIdleTm(mCurrentIdler); |
| 699 | } else { |
| 700 | if (DEBUG) { |
| 701 | Slog.w(TAG, "Asked to finish idle maintenance but we're done already"); |
| 702 | } |
| 703 | } |
| 704 | } |
| 705 | |
| 706 | // Actual finalization of the idle maintenance sequence |
| 707 | void stopIdleMaintenanceTm() { |
| 708 | if (mLastIdler != null) { |
| 709 | if (DEBUG) { |
| 710 | Slog.i(TAG, "*** Idle maintenance shutdown ***"); |
| 711 | } |
| 712 | mWakeLock.setWorkSource(mSystemWorkSource); |
| 713 | mLastIdler = mCurrentIdler = null; |
| 714 | updateIdleMaintenanceStateTm(false); // resets 'started' and schedules next window |
| 715 | mWakeLock.release(); |
| 716 | } else { |
| 717 | Slog.e(TAG, "ERROR: idle shutdown but invariants not held. last=" + mLastIdler |
| 718 | + " cur=" + mCurrentIdler + " size=" + mIdleServiceQueue.size()); |
| 719 | } |
| 720 | } |
| 721 | |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 722 | private long getNextIdleMaintenanceIntervalStartFromNow() { |
| 723 | return mLastIdleMaintenanceStartTimeMillis + MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS |
| 724 | - SystemClock.elapsedRealtime(); |
| 725 | } |
| 726 | |
Dianne Hackborn | 35f72be | 2013-09-16 10:57:39 -0700 | [diff] [blame] | 727 | private boolean deviceStatePermitsIdleMaintenanceStart(boolean noisy) { |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 728 | final int minBatteryLevel = isBatteryCharging() |
Svetoslav | f23b64d | 2013-04-25 14:45:54 -0700 | [diff] [blame] | 729 | ? MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING |
| 730 | : MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_NOT_CHARGING; |
Dianne Hackborn | 35f72be | 2013-09-16 10:57:39 -0700 | [diff] [blame] | 731 | boolean allowed = (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 732 | && mBatteryService.getBatteryLevel() > minBatteryLevel); |
Dianne Hackborn | 35f72be | 2013-09-16 10:57:39 -0700 | [diff] [blame] | 733 | if (!allowed && noisy) { |
| 734 | Slog.i("IdleMaintenance", "Idle maintenance not allowed due to power"); |
| 735 | } |
| 736 | return allowed; |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 737 | } |
| 738 | |
Dianne Hackborn | 35f72be | 2013-09-16 10:57:39 -0700 | [diff] [blame] | 739 | private boolean lastUserActivityPermitsIdleMaintenanceStart(boolean noisy) { |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 740 | // The last time the user poked the device is above the threshold. |
Dianne Hackborn | 35f72be | 2013-09-16 10:57:39 -0700 | [diff] [blame] | 741 | boolean allowed = (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 742 | && SystemClock.elapsedRealtime() - mLastUserActivityElapsedTimeMillis |
| 743 | > MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START); |
Dianne Hackborn | 35f72be | 2013-09-16 10:57:39 -0700 | [diff] [blame] | 744 | if (!allowed && noisy) { |
| 745 | Slog.i("IdleMaintenance", "Idle maintenance not allowed due to last user activity"); |
| 746 | } |
| 747 | return allowed; |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 748 | } |
| 749 | |
Dianne Hackborn | 35f72be | 2013-09-16 10:57:39 -0700 | [diff] [blame] | 750 | private boolean lastRunPermitsIdleMaintenanceStart(boolean noisy) { |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 751 | // Enough time passed since the last maintenance run. |
Dianne Hackborn | 35f72be | 2013-09-16 10:57:39 -0700 | [diff] [blame] | 752 | boolean allowed = SystemClock.elapsedRealtime() - mLastIdleMaintenanceStartTimeMillis |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 753 | > MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS; |
Dianne Hackborn | 35f72be | 2013-09-16 10:57:39 -0700 | [diff] [blame] | 754 | if (!allowed && noisy) { |
| 755 | Slog.i("IdleMaintenance", "Idle maintenance not allowed due time since last"); |
| 756 | } |
| 757 | return allowed; |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 758 | } |
| 759 | |
| 760 | private boolean lastUserActivityPermitsIdleMaintenanceRunning() { |
| 761 | // The user is not using the device. |
| 762 | return (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID); |
| 763 | } |
| 764 | |
| 765 | private boolean batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning() { |
| 766 | // Battery not too low and the maintenance duration did not timeout. |
| 767 | return (mBatteryService.getBatteryLevel() > MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_RUNNING |
| 768 | && mLastIdleMaintenanceStartTimeMillis + MAX_IDLE_MAINTENANCE_DURATION |
| 769 | > SystemClock.elapsedRealtime()); |
| 770 | } |
| 771 | |
| 772 | private boolean isBatteryCharging() { |
| 773 | return mBatteryService.getPlugType() > 0 |
| 774 | && mBatteryService.getInvalidCharger() == 0; |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 775 | } |
| 776 | |
| 777 | @Override |
| 778 | public void onReceive(Context context, Intent intent) { |
| 779 | if (DEBUG) { |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 780 | Log.i(TAG, intent.getAction()); |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 781 | } |
| 782 | String action = intent.getAction(); |
| 783 | if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 784 | // We care about battery only if maintenance is in progress so we can |
| 785 | // stop it if battery is too low. Note that here we assume that the |
| 786 | // maintenance clients are properly holding a wake lock. We will |
| 787 | // refactor the maintenance to use services instead of intents for the |
| 788 | // next release. The only client for this for now is internal an holds |
| 789 | // a wake lock correctly. |
| 790 | if (mIdleMaintenanceStarted) { |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 791 | updateIdleMaintenanceStateTm(false); |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 792 | } |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 793 | } else if (Intent.ACTION_SCREEN_ON.equals(action) |
| 794 | || Intent.ACTION_DREAMING_STOPPED.equals(action)) { |
| 795 | mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID; |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 796 | // Unschedule any future updates since we already know that maintenance |
| 797 | // cannot be performed since the user is back. |
| 798 | unscheduleUpdateIdleMaintenanceState(); |
| 799 | // If the screen went on/stopped dreaming, we know the user is using the |
| 800 | // device which means that idle maintenance should be stopped if running. |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 801 | updateIdleMaintenanceStateTm(false); |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 802 | } else if (Intent.ACTION_SCREEN_OFF.equals(action) |
| 803 | || Intent.ACTION_DREAMING_STARTED.equals(action)) { |
| 804 | mLastUserActivityElapsedTimeMillis = SystemClock.elapsedRealtime(); |
Svetoslav | 6a08a12 | 2013-05-03 11:24:26 -0700 | [diff] [blame] | 805 | // If screen went off/started dreaming, we may be able to start idle maintenance |
| 806 | // after the minimal user inactivity elapses. We schedule an alarm for when |
| 807 | // this timeout elapses since the device may go to sleep by then. |
| 808 | scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START); |
| 809 | } else if (ACTION_UPDATE_IDLE_MAINTENANCE_STATE.equals(action)) { |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 810 | updateIdleMaintenanceStateTm(false); |
Dianne Hackborn | 35f72be | 2013-09-16 10:57:39 -0700 | [diff] [blame] | 811 | } else if (ACTION_FORCE_IDLE_MAINTENANCE.equals(action)) { |
| 812 | long now = SystemClock.elapsedRealtime() - 1; |
| 813 | mLastUserActivityElapsedTimeMillis = now - MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START; |
| 814 | mLastIdleMaintenanceStartTimeMillis = now - MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS; |
Christopher Tate | d417d62 | 2013-08-19 16:14:25 -0700 | [diff] [blame] | 815 | updateIdleMaintenanceStateTm(true); |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 816 | } |
Svetoslav | b3038ec | 2013-02-13 14:39:30 -0800 | [diff] [blame] | 817 | } |
| 818 | } |