1 /** 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations 14 * under the License. 15 */ 16 17 package com.android.server.usage; 18 19 import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT; 20 import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED; 21 import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK; 22 import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED; 23 import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; 24 import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION; 25 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; 26 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER; 27 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; 28 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; 29 30 import android.app.usage.AppStandbyInfo; 31 import android.app.usage.UsageStatsManager; 32 import android.os.SystemClock; 33 import android.util.ArrayMap; 34 import android.util.AtomicFile; 35 import android.util.Slog; 36 import android.util.SparseArray; 37 import android.util.TimeUtils; 38 import android.util.Xml; 39 40 import com.android.internal.annotations.VisibleForTesting; 41 import com.android.internal.util.FastXmlSerializer; 42 import com.android.internal.util.IndentingPrintWriter; 43 44 import libcore.io.IoUtils; 45 46 import org.xmlpull.v1.XmlPullParser; 47 import org.xmlpull.v1.XmlPullParserException; 48 49 import java.io.BufferedOutputStream; 50 import java.io.BufferedReader; 51 import java.io.File; 52 import java.io.FileInputStream; 53 import java.io.FileOutputStream; 54 import java.io.FileReader; 55 import java.io.IOException; 56 import java.nio.charset.StandardCharsets; 57 import java.util.ArrayList; 58 59 /** 60 * Keeps track of recent active state changes in apps. 61 * Access should be guarded by a lock by the caller. 62 */ 63 public class AppIdleHistory { 64 65 private static final String TAG = "AppIdleHistory"; 66 67 private static final boolean DEBUG = AppStandbyController.DEBUG; 68 69 // History for all users and all packages 70 private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>(); 71 private static final long ONE_MINUTE = 60 * 1000; 72 73 private static final int STANDBY_BUCKET_UNKNOWN = -1; 74 75 @VisibleForTesting 76 static final String APP_IDLE_FILENAME = "app_idle_stats.xml"; 77 private static final String TAG_PACKAGES = "packages"; 78 private static final String TAG_PACKAGE = "package"; 79 private static final String ATTR_NAME = "name"; 80 // Screen on timebase time when app was last used 81 private static final String ATTR_SCREEN_IDLE = "screenIdleTime"; 82 // Elapsed timebase time when app was last used 83 private static final String ATTR_ELAPSED_IDLE = "elapsedIdleTime"; 84 // Elapsed timebase time when the app bucket was last predicted externally 85 private static final String ATTR_LAST_PREDICTED_TIME = "lastPredictedTime"; 86 // The standby bucket for the app 87 private static final String ATTR_CURRENT_BUCKET = "appLimitBucket"; 88 // The reason the app was put in the above bucket 89 private static final String ATTR_BUCKETING_REASON = "bucketReason"; 90 // The last time a job was run for this app 91 private static final String ATTR_LAST_RUN_JOB_TIME = "lastJobRunTime"; 92 // The time when the forced active state can be overridden. 93 private static final String ATTR_BUCKET_ACTIVE_TIMEOUT_TIME = "activeTimeoutTime"; 94 // The time when the forced working_set state can be overridden. 95 private static final String ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME = "workingSetTimeoutTime"; 96 97 // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot) 98 private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration 99 private long mElapsedDuration; // Total device on duration since device was "born" 100 101 // screen on time = mScreenOnDuration + (timeNow - mScreenOnSnapshot) 102 private long mScreenOnSnapshot; // Elapsed time snapshot when last write of mScreenOnDuration 103 private long mScreenOnDuration; // Total screen on duration since device was "born" 104 105 private final File mStorageDir; 106 107 private boolean mScreenOn; 108 109 static class AppUsageHistory { 110 // Last used time using elapsed timebase 111 long lastUsedElapsedTime; 112 // Last used time using screen_on timebase 113 long lastUsedScreenTime; 114 // Last predicted time using elapsed timebase 115 long lastPredictedTime; 116 // Last predicted bucket 117 @UsageStatsManager.StandbyBuckets 118 int lastPredictedBucket = STANDBY_BUCKET_UNKNOWN; 119 // Standby bucket 120 @UsageStatsManager.StandbyBuckets 121 int currentBucket; 122 // Reason for setting the standby bucket. The value here is a combination of 123 // one of UsageStatsManager.REASON_MAIN_* and one (or none) of 124 // UsageStatsManager.REASON_SUB_*. Also see REASON_MAIN_MASK and REASON_SUB_MASK. 125 int bucketingReason; 126 // In-memory only, last bucket for which the listeners were informed 127 int lastInformedBucket; 128 // The last time a job was run for this app, using elapsed timebase 129 long lastJobRunTime; 130 // When should the bucket active state timeout, in elapsed timebase, if greater than 131 // lastUsedElapsedTime. 132 // This is used to keep the app in a high bucket regardless of other timeouts and 133 // predictions. 134 long bucketActiveTimeoutTime; 135 // If there's a forced working_set state, this is when it times out. This can be sitting 136 // under any active state timeout, so that it becomes applicable after the active state 137 // timeout expires. 138 long bucketWorkingSetTimeoutTime; 139 } 140 AppIdleHistory(File storageDir, long elapsedRealtime)141 AppIdleHistory(File storageDir, long elapsedRealtime) { 142 mElapsedSnapshot = elapsedRealtime; 143 mScreenOnSnapshot = elapsedRealtime; 144 mStorageDir = storageDir; 145 readScreenOnTime(); 146 } 147 updateDisplay(boolean screenOn, long elapsedRealtime)148 public void updateDisplay(boolean screenOn, long elapsedRealtime) { 149 if (screenOn == mScreenOn) return; 150 151 mScreenOn = screenOn; 152 if (mScreenOn) { 153 mScreenOnSnapshot = elapsedRealtime; 154 } else { 155 mScreenOnDuration += elapsedRealtime - mScreenOnSnapshot; 156 mElapsedDuration += elapsedRealtime - mElapsedSnapshot; 157 mElapsedSnapshot = elapsedRealtime; 158 } 159 if (DEBUG) Slog.d(TAG, "mScreenOnSnapshot=" + mScreenOnSnapshot 160 + ", mScreenOnDuration=" + mScreenOnDuration 161 + ", mScreenOn=" + mScreenOn); 162 } 163 getScreenOnTime(long elapsedRealtime)164 public long getScreenOnTime(long elapsedRealtime) { 165 long screenOnTime = mScreenOnDuration; 166 if (mScreenOn) { 167 screenOnTime += elapsedRealtime - mScreenOnSnapshot; 168 } 169 return screenOnTime; 170 } 171 172 @VisibleForTesting getScreenOnTimeFile()173 File getScreenOnTimeFile() { 174 return new File(mStorageDir, "screen_on_time"); 175 } 176 readScreenOnTime()177 private void readScreenOnTime() { 178 File screenOnTimeFile = getScreenOnTimeFile(); 179 if (screenOnTimeFile.exists()) { 180 try { 181 BufferedReader reader = new BufferedReader(new FileReader(screenOnTimeFile)); 182 mScreenOnDuration = Long.parseLong(reader.readLine()); 183 mElapsedDuration = Long.parseLong(reader.readLine()); 184 reader.close(); 185 } catch (IOException | NumberFormatException e) { 186 } 187 } else { 188 writeScreenOnTime(); 189 } 190 } 191 writeScreenOnTime()192 private void writeScreenOnTime() { 193 AtomicFile screenOnTimeFile = new AtomicFile(getScreenOnTimeFile()); 194 FileOutputStream fos = null; 195 try { 196 fos = screenOnTimeFile.startWrite(); 197 fos.write((Long.toString(mScreenOnDuration) + "\n" 198 + Long.toString(mElapsedDuration) + "\n").getBytes()); 199 screenOnTimeFile.finishWrite(fos); 200 } catch (IOException ioe) { 201 screenOnTimeFile.failWrite(fos); 202 } 203 } 204 205 /** 206 * To be called periodically to keep track of elapsed time when app idle times are written 207 */ writeAppIdleDurations()208 public void writeAppIdleDurations() { 209 final long elapsedRealtime = SystemClock.elapsedRealtime(); 210 // Only bump up and snapshot the elapsed time. Don't change screen on duration. 211 mElapsedDuration += elapsedRealtime - mElapsedSnapshot; 212 mElapsedSnapshot = elapsedRealtime; 213 writeScreenOnTime(); 214 } 215 216 /** 217 * Mark the app as used and update the bucket if necessary. If there is a timeout specified 218 * that's in the future, then the usage event is temporary and keeps the app in the specified 219 * bucket at least until the timeout is reached. This can be used to keep the app in an 220 * elevated bucket for a while until some important task gets to run. 221 * @param appUsageHistory the usage record for the app being updated 222 * @param packageName name of the app being updated, for logging purposes 223 * @param newBucket the bucket to set the app to 224 * @param usageReason the sub-reason for usage, one of REASON_SUB_USAGE_* 225 * @param elapsedRealtime mark as used time if non-zero 226 * @param timeout set the timeout of the specified bucket, if non-zero. Can only be used 227 * with bucket values of ACTIVE and WORKING_SET. 228 * @return 229 */ reportUsage(AppUsageHistory appUsageHistory, String packageName, int newBucket, int usageReason, long elapsedRealtime, long timeout)230 public AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName, 231 int newBucket, int usageReason, long elapsedRealtime, long timeout) { 232 // Set the timeout if applicable 233 if (timeout > elapsedRealtime) { 234 // Convert to elapsed timebase 235 final long timeoutTime = mElapsedDuration + (timeout - mElapsedSnapshot); 236 if (newBucket == STANDBY_BUCKET_ACTIVE) { 237 appUsageHistory.bucketActiveTimeoutTime = Math.max(timeoutTime, 238 appUsageHistory.bucketActiveTimeoutTime); 239 } else if (newBucket == STANDBY_BUCKET_WORKING_SET) { 240 appUsageHistory.bucketWorkingSetTimeoutTime = Math.max(timeoutTime, 241 appUsageHistory.bucketWorkingSetTimeoutTime); 242 } else { 243 throw new IllegalArgumentException("Cannot set a timeout on bucket=" + 244 newBucket); 245 } 246 } 247 248 if (elapsedRealtime != 0) { 249 appUsageHistory.lastUsedElapsedTime = mElapsedDuration 250 + (elapsedRealtime - mElapsedSnapshot); 251 appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); 252 } 253 254 if (appUsageHistory.currentBucket > newBucket) { 255 appUsageHistory.currentBucket = newBucket; 256 if (DEBUG) { 257 Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory 258 .currentBucket 259 + ", reason=0x0" + Integer.toHexString(appUsageHistory.bucketingReason)); 260 } 261 } 262 appUsageHistory.bucketingReason = REASON_MAIN_USAGE | usageReason; 263 264 return appUsageHistory; 265 } 266 267 /** 268 * Mark the app as used and update the bucket if necessary. If there is a timeout specified 269 * that's in the future, then the usage event is temporary and keeps the app in the specified 270 * bucket at least until the timeout is reached. This can be used to keep the app in an 271 * elevated bucket for a while until some important task gets to run. 272 * @param packageName 273 * @param userId 274 * @param newBucket the bucket to set the app to 275 * @param usageReason sub reason for usage 276 * @param nowElapsed mark as used time if non-zero 277 * @param timeout set the timeout of the specified bucket, if non-zero. Can only be used 278 * with bucket values of ACTIVE and WORKING_SET. 279 * @return 280 */ reportUsage(String packageName, int userId, int newBucket, int usageReason, long nowElapsed, long timeout)281 public AppUsageHistory reportUsage(String packageName, int userId, int newBucket, 282 int usageReason, long nowElapsed, long timeout) { 283 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 284 AppUsageHistory history = getPackageHistory(userHistory, packageName, nowElapsed, true); 285 return reportUsage(history, packageName, newBucket, usageReason, nowElapsed, timeout); 286 } 287 getUserHistory(int userId)288 private ArrayMap<String, AppUsageHistory> getUserHistory(int userId) { 289 ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId); 290 if (userHistory == null) { 291 userHistory = new ArrayMap<>(); 292 mIdleHistory.put(userId, userHistory); 293 readAppIdleTimes(userId, userHistory); 294 } 295 return userHistory; 296 } 297 getPackageHistory(ArrayMap<String, AppUsageHistory> userHistory, String packageName, long elapsedRealtime, boolean create)298 private AppUsageHistory getPackageHistory(ArrayMap<String, AppUsageHistory> userHistory, 299 String packageName, long elapsedRealtime, boolean create) { 300 AppUsageHistory appUsageHistory = userHistory.get(packageName); 301 if (appUsageHistory == null && create) { 302 appUsageHistory = new AppUsageHistory(); 303 appUsageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime); 304 appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); 305 appUsageHistory.lastPredictedTime = getElapsedTime(0); 306 appUsageHistory.currentBucket = STANDBY_BUCKET_NEVER; 307 appUsageHistory.bucketingReason = REASON_MAIN_DEFAULT; 308 appUsageHistory.lastInformedBucket = -1; 309 appUsageHistory.lastJobRunTime = Long.MIN_VALUE; // long long time ago 310 userHistory.put(packageName, appUsageHistory); 311 } 312 return appUsageHistory; 313 } 314 onUserRemoved(int userId)315 public void onUserRemoved(int userId) { 316 mIdleHistory.remove(userId); 317 } 318 isIdle(String packageName, int userId, long elapsedRealtime)319 public boolean isIdle(String packageName, int userId, long elapsedRealtime) { 320 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 321 AppUsageHistory appUsageHistory = 322 getPackageHistory(userHistory, packageName, elapsedRealtime, true); 323 return appUsageHistory.currentBucket >= STANDBY_BUCKET_RARE; 324 } 325 getAppUsageHistory(String packageName, int userId, long elapsedRealtime)326 public AppUsageHistory getAppUsageHistory(String packageName, int userId, 327 long elapsedRealtime) { 328 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 329 AppUsageHistory appUsageHistory = 330 getPackageHistory(userHistory, packageName, elapsedRealtime, true); 331 return appUsageHistory; 332 } 333 setAppStandbyBucket(String packageName, int userId, long elapsedRealtime, int bucket, int reason)334 public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime, 335 int bucket, int reason) { 336 setAppStandbyBucket(packageName, userId, elapsedRealtime, bucket, reason, false); 337 } 338 setAppStandbyBucket(String packageName, int userId, long elapsedRealtime, int bucket, int reason, boolean resetTimeout)339 public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime, 340 int bucket, int reason, boolean resetTimeout) { 341 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 342 AppUsageHistory appUsageHistory = 343 getPackageHistory(userHistory, packageName, elapsedRealtime, true); 344 appUsageHistory.currentBucket = bucket; 345 appUsageHistory.bucketingReason = reason; 346 347 final long elapsed = getElapsedTime(elapsedRealtime); 348 349 if ((reason & REASON_MAIN_MASK) == REASON_MAIN_PREDICTED) { 350 appUsageHistory.lastPredictedTime = elapsed; 351 appUsageHistory.lastPredictedBucket = bucket; 352 } 353 if (resetTimeout) { 354 appUsageHistory.bucketActiveTimeoutTime = elapsed; 355 appUsageHistory.bucketWorkingSetTimeoutTime = elapsed; 356 } 357 if (DEBUG) { 358 Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket 359 + ", reason=0x0" + Integer.toHexString(appUsageHistory.bucketingReason)); 360 } 361 } 362 363 /** 364 * Update the prediction for the app but don't change the actual bucket 365 * @param app The app for which the prediction was made 366 * @param elapsedTimeAdjusted The elapsed time in the elapsed duration timebase 367 * @param bucket The predicted bucket 368 */ updateLastPrediction(AppUsageHistory app, long elapsedTimeAdjusted, int bucket)369 public void updateLastPrediction(AppUsageHistory app, long elapsedTimeAdjusted, int bucket) { 370 app.lastPredictedTime = elapsedTimeAdjusted; 371 app.lastPredictedBucket = bucket; 372 } 373 374 /** 375 * Marks the last time a job was run, with the given elapsedRealtime. The time stored is 376 * based on the elapsed timebase. 377 * @param packageName 378 * @param userId 379 * @param elapsedRealtime 380 */ setLastJobRunTime(String packageName, int userId, long elapsedRealtime)381 public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) { 382 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 383 AppUsageHistory appUsageHistory = 384 getPackageHistory(userHistory, packageName, elapsedRealtime, true); 385 appUsageHistory.lastJobRunTime = getElapsedTime(elapsedRealtime); 386 } 387 388 /** 389 * Returns the time since the last job was run for this app. This can be larger than the 390 * current elapsedRealtime, in case it happened before boot or a really large value if no jobs 391 * were ever run. 392 * @param packageName 393 * @param userId 394 * @param elapsedRealtime 395 * @return 396 */ getTimeSinceLastJobRun(String packageName, int userId, long elapsedRealtime)397 public long getTimeSinceLastJobRun(String packageName, int userId, long elapsedRealtime) { 398 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 399 AppUsageHistory appUsageHistory = 400 getPackageHistory(userHistory, packageName, elapsedRealtime, false); 401 // Don't adjust the default, else it'll wrap around to a positive value 402 if (appUsageHistory == null || appUsageHistory.lastJobRunTime == Long.MIN_VALUE) { 403 return Long.MAX_VALUE; 404 } 405 return getElapsedTime(elapsedRealtime) - appUsageHistory.lastJobRunTime; 406 } 407 getAppStandbyBucket(String packageName, int userId, long elapsedRealtime)408 public int getAppStandbyBucket(String packageName, int userId, long elapsedRealtime) { 409 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 410 AppUsageHistory appUsageHistory = 411 getPackageHistory(userHistory, packageName, elapsedRealtime, false); 412 return appUsageHistory == null ? STANDBY_BUCKET_NEVER : appUsageHistory.currentBucket; 413 } 414 getAppStandbyBuckets(int userId, boolean appIdleEnabled)415 public ArrayList<AppStandbyInfo> getAppStandbyBuckets(int userId, boolean appIdleEnabled) { 416 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 417 int size = userHistory.size(); 418 ArrayList<AppStandbyInfo> buckets = new ArrayList<>(size); 419 for (int i = 0; i < size; i++) { 420 buckets.add(new AppStandbyInfo(userHistory.keyAt(i), 421 appIdleEnabled ? userHistory.valueAt(i).currentBucket : STANDBY_BUCKET_ACTIVE)); 422 } 423 return buckets; 424 } 425 getAppStandbyReason(String packageName, int userId, long elapsedRealtime)426 public int getAppStandbyReason(String packageName, int userId, long elapsedRealtime) { 427 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 428 AppUsageHistory appUsageHistory = 429 getPackageHistory(userHistory, packageName, elapsedRealtime, false); 430 return appUsageHistory != null ? appUsageHistory.bucketingReason : 0; 431 } 432 getElapsedTime(long elapsedRealtime)433 public long getElapsedTime(long elapsedRealtime) { 434 return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration); 435 } 436 437 /* Returns the new standby bucket the app is assigned to */ setIdle(String packageName, int userId, boolean idle, long elapsedRealtime)438 public int setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) { 439 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 440 AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, 441 elapsedRealtime, true); 442 if (idle) { 443 appUsageHistory.currentBucket = STANDBY_BUCKET_RARE; 444 appUsageHistory.bucketingReason = REASON_MAIN_FORCED; 445 } else { 446 appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE; 447 // This is to pretend that the app was just used, don't freeze the state anymore. 448 appUsageHistory.bucketingReason = REASON_MAIN_USAGE | REASON_SUB_USAGE_USER_INTERACTION; 449 } 450 return appUsageHistory.currentBucket; 451 } 452 clearUsage(String packageName, int userId)453 public void clearUsage(String packageName, int userId) { 454 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 455 userHistory.remove(packageName); 456 } 457 shouldInformListeners(String packageName, int userId, long elapsedRealtime, int bucket)458 boolean shouldInformListeners(String packageName, int userId, 459 long elapsedRealtime, int bucket) { 460 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 461 AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, 462 elapsedRealtime, true); 463 if (appUsageHistory.lastInformedBucket != bucket) { 464 appUsageHistory.lastInformedBucket = bucket; 465 return true; 466 } 467 return false; 468 } 469 470 /** 471 * Returns the index in the arrays of screenTimeThresholds and elapsedTimeThresholds 472 * that corresponds to how long since the app was used. 473 * @param packageName 474 * @param userId 475 * @param elapsedRealtime current time 476 * @param screenTimeThresholds Array of screen times, in ascending order, first one is 0 477 * @param elapsedTimeThresholds Array of elapsed time, in ascending order, first one is 0 478 * @return The index whose values the app's used time exceeds (in both arrays) 479 */ getThresholdIndex(String packageName, int userId, long elapsedRealtime, long[] screenTimeThresholds, long[] elapsedTimeThresholds)480 int getThresholdIndex(String packageName, int userId, long elapsedRealtime, 481 long[] screenTimeThresholds, long[] elapsedTimeThresholds) { 482 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 483 AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, 484 elapsedRealtime, false); 485 // If we don't have any state for the app, assume never used 486 if (appUsageHistory == null) return screenTimeThresholds.length - 1; 487 488 long screenOnDelta = getScreenOnTime(elapsedRealtime) - appUsageHistory.lastUsedScreenTime; 489 long elapsedDelta = getElapsedTime(elapsedRealtime) - appUsageHistory.lastUsedElapsedTime; 490 491 if (DEBUG) Slog.d(TAG, packageName 492 + " lastUsedScreen=" + appUsageHistory.lastUsedScreenTime 493 + " lastUsedElapsed=" + appUsageHistory.lastUsedElapsedTime); 494 if (DEBUG) Slog.d(TAG, packageName + " screenOn=" + screenOnDelta 495 + ", elapsed=" + elapsedDelta); 496 for (int i = screenTimeThresholds.length - 1; i >= 0; i--) { 497 if (screenOnDelta >= screenTimeThresholds[i] 498 && elapsedDelta >= elapsedTimeThresholds[i]) { 499 return i; 500 } 501 } 502 return 0; 503 } 504 505 @VisibleForTesting getUserFile(int userId)506 File getUserFile(int userId) { 507 return new File(new File(new File(mStorageDir, "users"), 508 Integer.toString(userId)), APP_IDLE_FILENAME); 509 } 510 511 512 /** 513 * Check if App Idle File exists on disk 514 * @param userId 515 * @return true if file exists 516 */ userFileExists(int userId)517 public boolean userFileExists(int userId) { 518 return getUserFile(userId).exists(); 519 } 520 readAppIdleTimes(int userId, ArrayMap<String, AppUsageHistory> userHistory)521 private void readAppIdleTimes(int userId, ArrayMap<String, AppUsageHistory> userHistory) { 522 FileInputStream fis = null; 523 try { 524 AtomicFile appIdleFile = new AtomicFile(getUserFile(userId)); 525 fis = appIdleFile.openRead(); 526 XmlPullParser parser = Xml.newPullParser(); 527 parser.setInput(fis, StandardCharsets.UTF_8.name()); 528 529 int type; 530 while ((type = parser.next()) != XmlPullParser.START_TAG 531 && type != XmlPullParser.END_DOCUMENT) { 532 // Skip 533 } 534 535 if (type != XmlPullParser.START_TAG) { 536 Slog.e(TAG, "Unable to read app idle file for user " + userId); 537 return; 538 } 539 if (!parser.getName().equals(TAG_PACKAGES)) { 540 return; 541 } 542 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 543 if (type == XmlPullParser.START_TAG) { 544 final String name = parser.getName(); 545 if (name.equals(TAG_PACKAGE)) { 546 final String packageName = parser.getAttributeValue(null, ATTR_NAME); 547 AppUsageHistory appUsageHistory = new AppUsageHistory(); 548 appUsageHistory.lastUsedElapsedTime = 549 Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE)); 550 appUsageHistory.lastUsedScreenTime = 551 Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE)); 552 appUsageHistory.lastPredictedTime = getLongValue(parser, 553 ATTR_LAST_PREDICTED_TIME, 0L); 554 String currentBucketString = parser.getAttributeValue(null, 555 ATTR_CURRENT_BUCKET); 556 appUsageHistory.currentBucket = currentBucketString == null 557 ? STANDBY_BUCKET_ACTIVE 558 : Integer.parseInt(currentBucketString); 559 String bucketingReason = 560 parser.getAttributeValue(null, ATTR_BUCKETING_REASON); 561 appUsageHistory.lastJobRunTime = getLongValue(parser, 562 ATTR_LAST_RUN_JOB_TIME, Long.MIN_VALUE); 563 appUsageHistory.bucketActiveTimeoutTime = getLongValue(parser, 564 ATTR_BUCKET_ACTIVE_TIMEOUT_TIME, 0L); 565 appUsageHistory.bucketWorkingSetTimeoutTime = getLongValue(parser, 566 ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME, 0L); 567 appUsageHistory.bucketingReason = REASON_MAIN_DEFAULT; 568 if (bucketingReason != null) { 569 try { 570 appUsageHistory.bucketingReason = 571 Integer.parseInt(bucketingReason, 16); 572 } catch (NumberFormatException nfe) { 573 } 574 } 575 appUsageHistory.lastInformedBucket = -1; 576 userHistory.put(packageName, appUsageHistory); 577 } 578 } 579 } 580 } catch (IOException | XmlPullParserException e) { 581 Slog.e(TAG, "Unable to read app idle file for user " + userId, e); 582 } finally { 583 IoUtils.closeQuietly(fis); 584 } 585 } 586 getLongValue(XmlPullParser parser, String attrName, long defValue)587 private long getLongValue(XmlPullParser parser, String attrName, long defValue) { 588 String value = parser.getAttributeValue(null, attrName); 589 if (value == null) return defValue; 590 return Long.parseLong(value); 591 } 592 writeAppIdleTimes(int userId)593 public void writeAppIdleTimes(int userId) { 594 FileOutputStream fos = null; 595 AtomicFile appIdleFile = new AtomicFile(getUserFile(userId)); 596 try { 597 fos = appIdleFile.startWrite(); 598 final BufferedOutputStream bos = new BufferedOutputStream(fos); 599 600 FastXmlSerializer xml = new FastXmlSerializer(); 601 xml.setOutput(bos, StandardCharsets.UTF_8.name()); 602 xml.startDocument(null, true); 603 xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 604 605 xml.startTag(null, TAG_PACKAGES); 606 607 ArrayMap<String,AppUsageHistory> userHistory = getUserHistory(userId); 608 final int N = userHistory.size(); 609 for (int i = 0; i < N; i++) { 610 String packageName = userHistory.keyAt(i); 611 // Skip any unexpected null package names 612 if (packageName == null) { 613 Slog.w(TAG, "Skipping App Idle write for unexpected null package"); 614 continue; 615 } 616 AppUsageHistory history = userHistory.valueAt(i); 617 xml.startTag(null, TAG_PACKAGE); 618 xml.attribute(null, ATTR_NAME, packageName); 619 xml.attribute(null, ATTR_ELAPSED_IDLE, 620 Long.toString(history.lastUsedElapsedTime)); 621 xml.attribute(null, ATTR_SCREEN_IDLE, 622 Long.toString(history.lastUsedScreenTime)); 623 xml.attribute(null, ATTR_LAST_PREDICTED_TIME, 624 Long.toString(history.lastPredictedTime)); 625 xml.attribute(null, ATTR_CURRENT_BUCKET, 626 Integer.toString(history.currentBucket)); 627 xml.attribute(null, ATTR_BUCKETING_REASON, 628 Integer.toHexString(history.bucketingReason)); 629 if (history.bucketActiveTimeoutTime > 0) { 630 xml.attribute(null, ATTR_BUCKET_ACTIVE_TIMEOUT_TIME, Long.toString(history 631 .bucketActiveTimeoutTime)); 632 } 633 if (history.bucketWorkingSetTimeoutTime > 0) { 634 xml.attribute(null, ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME, Long.toString(history 635 .bucketWorkingSetTimeoutTime)); 636 } 637 if (history.lastJobRunTime != Long.MIN_VALUE) { 638 xml.attribute(null, ATTR_LAST_RUN_JOB_TIME, Long.toString(history 639 .lastJobRunTime)); 640 } 641 xml.endTag(null, TAG_PACKAGE); 642 } 643 644 xml.endTag(null, TAG_PACKAGES); 645 xml.endDocument(); 646 appIdleFile.finishWrite(fos); 647 } catch (Exception e) { 648 appIdleFile.failWrite(fos); 649 Slog.e(TAG, "Error writing app idle file for user " + userId, e); 650 } 651 } 652 dump(IndentingPrintWriter idpw, int userId, String pkg)653 public void dump(IndentingPrintWriter idpw, int userId, String pkg) { 654 idpw.println("App Standby States:"); 655 idpw.increaseIndent(); 656 ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId); 657 final long elapsedRealtime = SystemClock.elapsedRealtime(); 658 final long totalElapsedTime = getElapsedTime(elapsedRealtime); 659 final long screenOnTime = getScreenOnTime(elapsedRealtime); 660 if (userHistory == null) return; 661 final int P = userHistory.size(); 662 for (int p = 0; p < P; p++) { 663 final String packageName = userHistory.keyAt(p); 664 final AppUsageHistory appUsageHistory = userHistory.valueAt(p); 665 if (pkg != null && !pkg.equals(packageName)) { 666 continue; 667 } 668 idpw.print("package=" + packageName); 669 idpw.print(" u=" + userId); 670 idpw.print(" bucket=" + appUsageHistory.currentBucket 671 + " reason=" 672 + UsageStatsManager.reasonToString(appUsageHistory.bucketingReason)); 673 idpw.print(" used="); 674 TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedElapsedTime, idpw); 675 idpw.print(" usedScr="); 676 TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw); 677 idpw.print(" lastPred="); 678 TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastPredictedTime, idpw); 679 idpw.print(" activeLeft="); 680 TimeUtils.formatDuration(appUsageHistory.bucketActiveTimeoutTime - totalElapsedTime, 681 idpw); 682 idpw.print(" wsLeft="); 683 TimeUtils.formatDuration(appUsageHistory.bucketWorkingSetTimeoutTime - totalElapsedTime, 684 idpw); 685 idpw.print(" lastJob="); 686 TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw); 687 idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n")); 688 idpw.println(); 689 } 690 idpw.println(); 691 idpw.print("totalElapsedTime="); 692 TimeUtils.formatDuration(getElapsedTime(elapsedRealtime), idpw); 693 idpw.println(); 694 idpw.print("totalScreenOnTime="); 695 TimeUtils.formatDuration(getScreenOnTime(elapsedRealtime), idpw); 696 idpw.println(); 697 idpw.decreaseIndent(); 698 } 699 } 700