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_BY_USER; 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_TIMEOUT; 24 import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; 25 import static android.app.usage.UsageStatsManager.REASON_SUB_MASK; 26 import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION; 27 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; 28 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER; 29 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; 30 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED; 31 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; 32 33 import static com.android.server.usage.AppStandbyController.isUserUsage; 34 35 import android.annotation.CurrentTimeMillisLong; 36 import android.annotation.ElapsedRealtimeLong; 37 import android.app.usage.AppStandbyInfo; 38 import android.app.usage.UsageStatsManager; 39 import android.os.SystemClock; 40 import android.util.ArrayMap; 41 import android.util.ArraySet; 42 import android.util.AtomicFile; 43 import android.util.IndentingPrintWriter; 44 import android.util.Slog; 45 import android.util.SparseArray; 46 import android.util.SparseLongArray; 47 import android.util.TimeUtils; 48 import android.util.Xml; 49 50 import com.android.internal.annotations.VisibleForTesting; 51 import com.android.internal.util.CollectionUtils; 52 import com.android.internal.util.FastXmlSerializer; 53 import com.android.internal.util.FrameworkStatsLog; 54 import com.android.internal.util.XmlUtils; 55 56 import libcore.io.IoUtils; 57 58 import org.xmlpull.v1.XmlPullParser; 59 import org.xmlpull.v1.XmlPullParserException; 60 61 import java.io.BufferedOutputStream; 62 import java.io.BufferedReader; 63 import java.io.File; 64 import java.io.FileInputStream; 65 import java.io.FileNotFoundException; 66 import java.io.FileOutputStream; 67 import java.io.FileReader; 68 import java.io.IOException; 69 import java.nio.charset.StandardCharsets; 70 import java.util.ArrayList; 71 import java.util.List; 72 73 /** 74 * Keeps track of recent active state changes in apps. 75 * Access should be guarded by a lock by the caller. 76 */ 77 public class AppIdleHistory { 78 79 private static final String TAG = "AppIdleHistory"; 80 81 private static final boolean DEBUG = AppStandbyController.DEBUG; 82 83 // History for all users and all packages 84 private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>(); 85 private static final long ONE_MINUTE = 60 * 1000; 86 87 // Only keep the persisted restore-to-rare apps list for 2 days. 88 static final long RESTORE_TO_RARE_APPS_LIST_EXPIRY = ONE_MINUTE * 60 * 24 * 2; 89 90 static final int STANDBY_BUCKET_UNKNOWN = -1; 91 92 /** 93 * The bucket beyond which apps are considered idle. Any apps in this bucket or lower are 94 * considered idle while those in higher buckets are not considered idle. 95 */ 96 static final int IDLE_BUCKET_CUTOFF = STANDBY_BUCKET_RARE; 97 98 /** Initial version of the xml containing the app idle stats ({@link #APP_IDLE_FILENAME}). */ 99 private static final int XML_VERSION_INITIAL = 0; 100 /** 101 * Allowed writing expiry times for any standby bucket instead of only active and working set. 102 * In previous version, we used to specify expiry times for active and working set as 103 * attributes: 104 * <pre> 105 * <package activeTimeoutTime="..." workingSetTimeoutTime="..." /> 106 * </pre> 107 * In this version, it is changed to: 108 * <pre> 109 * <package> 110 * <expiryTimes> 111 * <item bucket="..." expiry="..." /> 112 * <item bucket="..." expiry="..." /> 113 * </expiryTimes> 114 * </package> 115 * </pre> 116 */ 117 private static final int XML_VERSION_ADD_BUCKET_EXPIRY_TIMES = 1; 118 /** Current version */ 119 private static final int XML_VERSION_CURRENT = XML_VERSION_ADD_BUCKET_EXPIRY_TIMES; 120 121 @VisibleForTesting 122 static final String APP_IDLE_FILENAME = "app_idle_stats.xml"; 123 private static final String TAG_PACKAGES = "packages"; 124 private static final String TAG_PACKAGE = "package"; 125 private static final String TAG_BUCKET_EXPIRY_TIMES = "expiryTimes"; 126 private static final String TAG_ITEM = "item"; 127 private static final String ATTR_NAME = "name"; 128 // Screen on timebase time when app was last used 129 private static final String ATTR_SCREEN_IDLE = "screenIdleTime"; 130 // Elapsed timebase time when app was last used 131 private static final String ATTR_ELAPSED_IDLE = "elapsedIdleTime"; 132 // Elapsed timebase time when app was last used by the user 133 private static final String ATTR_LAST_USED_BY_USER_ELAPSED = "lastUsedByUserElapsedTime"; 134 // Elapsed timebase time when the app bucket was last predicted externally 135 private static final String ATTR_LAST_PREDICTED_TIME = "lastPredictedTime"; 136 // The standby bucket for the app 137 private static final String ATTR_CURRENT_BUCKET = "appLimitBucket"; 138 // The reason the app was put in the above bucket 139 private static final String ATTR_BUCKETING_REASON = "bucketReason"; 140 // The last time a job was run for this app 141 private static final String ATTR_LAST_RUN_JOB_TIME = "lastJobRunTime"; 142 // The time when the forced active state can be overridden. 143 private static final String ATTR_BUCKET_ACTIVE_TIMEOUT_TIME = "activeTimeoutTime"; 144 // The time when the forced working_set state can be overridden. 145 private static final String ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME = "workingSetTimeoutTime"; 146 // The standby bucket value 147 private static final String ATTR_BUCKET = "bucket"; 148 // The time when the forced bucket state can be overridde. 149 private static final String ATTR_EXPIRY_TIME = "expiry"; 150 // Elapsed timebase time when the app was last marked for restriction. 151 private static final String ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED = 152 "lastRestrictionAttemptElapsedTime"; 153 // Reason why the app was last marked for restriction. 154 private static final String ATTR_LAST_RESTRICTION_ATTEMPT_REASON = 155 "lastRestrictionAttemptReason"; 156 // The next estimated launch time of the app, in ms since epoch. 157 private static final String ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME = "nextEstimatedAppLaunchTime"; 158 // Version of the xml file. 159 private static final String ATTR_VERSION = "version"; 160 161 // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot) 162 private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration 163 private long mElapsedDuration; // Total device on duration since device was "born" 164 165 // screen on time = mScreenOnDuration + (timeNow - mScreenOnSnapshot) 166 private long mScreenOnSnapshot; // Elapsed time snapshot when last write of mScreenOnDuration 167 private long mScreenOnDuration; // Total screen on duration since device was "born" 168 169 private final File mStorageDir; 170 171 private boolean mScreenOn; 172 173 static class AppUsageHistory { 174 // Last used time (including system usage), using elapsed timebase 175 long lastUsedElapsedTime; 176 // Last time the user used the app, using elapsed timebase 177 long lastUsedByUserElapsedTime; 178 // Last used time using screen_on timebase 179 long lastUsedScreenTime; 180 // Last predicted time using elapsed timebase 181 long lastPredictedTime; 182 // Last predicted bucket 183 @UsageStatsManager.StandbyBuckets 184 int lastPredictedBucket = STANDBY_BUCKET_UNKNOWN; 185 // Standby bucket 186 @UsageStatsManager.StandbyBuckets 187 int currentBucket; 188 // Reason for setting the standby bucket. The value here is a combination of 189 // one of UsageStatsManager.REASON_MAIN_* and one (or none) of 190 // UsageStatsManager.REASON_SUB_*. Also see REASON_MAIN_MASK and REASON_SUB_MASK. 191 int bucketingReason; 192 // In-memory only, last bucket for which the listeners were informed 193 int lastInformedBucket; 194 // The last time a job was run for this app, using elapsed timebase 195 long lastJobRunTime; 196 // The estimated time the app will be launched next, in milliseconds since epoch. 197 @CurrentTimeMillisLong 198 long nextEstimatedLaunchTime; 199 // Contains standby buckets that apps were forced into and the corresponding expiry times 200 // (in elapsed timebase) for each bucket state. App will stay in the highest bucket until 201 // it's expiry time is elapsed and will be moved to the next highest bucket. 202 SparseLongArray bucketExpiryTimesMs; 203 // The last time an agent attempted to put the app into the RESTRICTED bucket. 204 long lastRestrictAttemptElapsedTime; 205 // The last reason the app was marked to be put into the RESTRICTED bucket. 206 int lastRestrictReason; 207 } 208 AppIdleHistory(File storageDir, long elapsedRealtime)209 AppIdleHistory(File storageDir, long elapsedRealtime) { 210 mElapsedSnapshot = elapsedRealtime; 211 mScreenOnSnapshot = elapsedRealtime; 212 mStorageDir = storageDir; 213 readScreenOnTime(); 214 } 215 updateDisplay(boolean screenOn, long elapsedRealtime)216 public void updateDisplay(boolean screenOn, long elapsedRealtime) { 217 if (screenOn == mScreenOn) return; 218 219 mScreenOn = screenOn; 220 if (mScreenOn) { 221 mScreenOnSnapshot = elapsedRealtime; 222 } else { 223 mScreenOnDuration += elapsedRealtime - mScreenOnSnapshot; 224 mElapsedDuration += elapsedRealtime - mElapsedSnapshot; 225 mElapsedSnapshot = elapsedRealtime; 226 } 227 if (DEBUG) Slog.d(TAG, "mScreenOnSnapshot=" + mScreenOnSnapshot 228 + ", mScreenOnDuration=" + mScreenOnDuration 229 + ", mScreenOn=" + mScreenOn); 230 } 231 getScreenOnTime(long elapsedRealtime)232 public long getScreenOnTime(long elapsedRealtime) { 233 long screenOnTime = mScreenOnDuration; 234 if (mScreenOn) { 235 screenOnTime += elapsedRealtime - mScreenOnSnapshot; 236 } 237 return screenOnTime; 238 } 239 240 @VisibleForTesting getScreenOnTimeFile()241 File getScreenOnTimeFile() { 242 return new File(mStorageDir, "screen_on_time"); 243 } 244 readScreenOnTime()245 private void readScreenOnTime() { 246 File screenOnTimeFile = getScreenOnTimeFile(); 247 if (screenOnTimeFile.exists()) { 248 try { 249 BufferedReader reader = new BufferedReader(new FileReader(screenOnTimeFile)); 250 mScreenOnDuration = Long.parseLong(reader.readLine()); 251 mElapsedDuration = Long.parseLong(reader.readLine()); 252 reader.close(); 253 } catch (IOException | NumberFormatException e) { 254 } 255 } else { 256 writeScreenOnTime(); 257 } 258 } 259 writeScreenOnTime()260 private void writeScreenOnTime() { 261 AtomicFile screenOnTimeFile = new AtomicFile(getScreenOnTimeFile()); 262 FileOutputStream fos = null; 263 try { 264 fos = screenOnTimeFile.startWrite(); 265 fos.write((Long.toString(mScreenOnDuration) + "\n" 266 + Long.toString(mElapsedDuration) + "\n").getBytes()); 267 screenOnTimeFile.finishWrite(fos); 268 } catch (IOException ioe) { 269 screenOnTimeFile.failWrite(fos); 270 } 271 } 272 273 /** 274 * To be called periodically to keep track of elapsed time when app idle times are written 275 */ writeAppIdleDurations()276 public void writeAppIdleDurations() { 277 final long elapsedRealtime = SystemClock.elapsedRealtime(); 278 // Only bump up and snapshot the elapsed time. Don't change screen on duration. 279 mElapsedDuration += elapsedRealtime - mElapsedSnapshot; 280 mElapsedSnapshot = elapsedRealtime; 281 writeScreenOnTime(); 282 } 283 getRestoreToRareAppsListFile(int userId)284 private File getRestoreToRareAppsListFile(int userId) { 285 return new File(getUserDirectory(userId), "restore_to_rare_apps_list"); 286 } 287 readRestoreToRareAppsList(int userId)288 public ArraySet<String> readRestoreToRareAppsList(int userId) { 289 File restoreToRareAppsListFile = getRestoreToRareAppsListFile(userId); 290 if (!restoreToRareAppsListFile.exists()) { 291 return null; 292 } 293 294 try (BufferedReader reader = 295 new BufferedReader(new FileReader(restoreToRareAppsListFile))) { 296 final ArraySet<String> appsList = new ArraySet<>(); 297 final long restoreTime = Long.parseLong(reader.readLine()); 298 if (System.currentTimeMillis() - restoreTime > RESTORE_TO_RARE_APPS_LIST_EXPIRY) { 299 // the apps list should only be kept around for 2 days 300 reader.close(); 301 restoreToRareAppsListFile.delete(); 302 return null; 303 } 304 String pkgName; 305 while ((pkgName = reader.readLine()) != null) { 306 appsList.add(pkgName); 307 } 308 return appsList; 309 } catch (IOException | NumberFormatException e) { 310 return null; 311 } 312 } 313 writeRestoreToRareAppsList(int userId, ArraySet<String> restoreAppsToRare)314 public void writeRestoreToRareAppsList(int userId, ArraySet<String> restoreAppsToRare) { 315 File fileHandle = getRestoreToRareAppsListFile(userId); 316 if (fileHandle.exists()) { 317 // don't update the persisted file - it should only be written once. 318 return; 319 } 320 AtomicFile restoreToRareAppsListFile = new AtomicFile(fileHandle); 321 FileOutputStream fos = null; 322 try { 323 fos = restoreToRareAppsListFile.startWrite(); 324 final StringBuilder sb = new StringBuilder(); 325 sb.append(System.currentTimeMillis()).append("\n"); 326 for (String pkgName : restoreAppsToRare) { 327 sb.append(pkgName).append("\n"); 328 } 329 fos.write(sb.toString().getBytes()); 330 restoreToRareAppsListFile.finishWrite(fos); 331 } catch (IOException ioe) { 332 restoreToRareAppsListFile.failWrite(fos); 333 } 334 } 335 336 /** 337 * Mark the app as used and update the bucket if necessary. If there is a expiry time specified 338 * that's in the future, then the usage event is temporary and keeps the app in the specified 339 * bucket at least until the expiry time is reached. This can be used to keep the app in an 340 * elevated bucket for a while until some important task gets to run. 341 * 342 * @param appUsageHistory the usage record for the app being updated 343 * @param packageName name of the app being updated, for logging purposes 344 * @param newBucket the bucket to set the app to 345 * @param usageReason the sub-reason for usage, one of REASON_SUB_USAGE_* 346 * @param nowElapsedRealtimeMs mark as used time if non-zero (in 347 * {@link SystemClock#elapsedRealtime()} time base) 348 * @param expiryElapsedRealtimeMs the expiry time for the specified bucket (in 349 * {@link SystemClock#elapsedRealtime()} time base) 350 * @return {@code appUsageHistory} 351 */ reportUsage(AppUsageHistory appUsageHistory, String packageName, int userId, int newBucket, int usageReason, long nowElapsedRealtimeMs, long expiryElapsedRealtimeMs)352 AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName, int userId, 353 int newBucket, int usageReason, 354 long nowElapsedRealtimeMs, long expiryElapsedRealtimeMs) { 355 int bucketingReason = REASON_MAIN_USAGE | usageReason; 356 final boolean isUserUsage = isUserUsage(bucketingReason); 357 358 if (appUsageHistory.currentBucket == STANDBY_BUCKET_RESTRICTED && !isUserUsage 359 && (appUsageHistory.bucketingReason & REASON_MAIN_MASK) != REASON_MAIN_TIMEOUT) { 360 // Only user usage should bring an app out of the RESTRICTED bucket, unless the app 361 // just timed out into RESTRICTED. 362 newBucket = STANDBY_BUCKET_RESTRICTED; 363 bucketingReason = appUsageHistory.bucketingReason; 364 } else { 365 // Set the expiry time if applicable 366 if (expiryElapsedRealtimeMs > nowElapsedRealtimeMs) { 367 // Convert to elapsed timebase 368 final long expiryTimeMs = getElapsedTime(expiryElapsedRealtimeMs); 369 if (appUsageHistory.bucketExpiryTimesMs == null) { 370 appUsageHistory.bucketExpiryTimesMs = new SparseLongArray(); 371 } 372 final long currentExpiryTimeMs = appUsageHistory.bucketExpiryTimesMs.get(newBucket); 373 appUsageHistory.bucketExpiryTimesMs.put(newBucket, 374 Math.max(expiryTimeMs, currentExpiryTimeMs)); 375 removeElapsedExpiryTimes(appUsageHistory, getElapsedTime(nowElapsedRealtimeMs)); 376 } 377 } 378 379 if (nowElapsedRealtimeMs != 0) { 380 appUsageHistory.lastUsedElapsedTime = mElapsedDuration 381 + (nowElapsedRealtimeMs - mElapsedSnapshot); 382 if (isUserUsage) { 383 appUsageHistory.lastUsedByUserElapsedTime = appUsageHistory.lastUsedElapsedTime; 384 } 385 appUsageHistory.lastUsedScreenTime = getScreenOnTime(nowElapsedRealtimeMs); 386 } 387 388 if (appUsageHistory.currentBucket >= newBucket) { 389 if (appUsageHistory.currentBucket > newBucket) { 390 appUsageHistory.currentBucket = newBucket; 391 logAppStandbyBucketChanged(packageName, userId, newBucket, bucketingReason); 392 } 393 appUsageHistory.bucketingReason = bucketingReason; 394 } 395 396 return appUsageHistory; 397 } 398 removeElapsedExpiryTimes(AppUsageHistory appUsageHistory, long elapsedTimeMs)399 private void removeElapsedExpiryTimes(AppUsageHistory appUsageHistory, long elapsedTimeMs) { 400 if (appUsageHistory.bucketExpiryTimesMs == null) { 401 return; 402 } 403 for (int i = appUsageHistory.bucketExpiryTimesMs.size() - 1; i >= 0; --i) { 404 if (appUsageHistory.bucketExpiryTimesMs.valueAt(i) < elapsedTimeMs) { 405 appUsageHistory.bucketExpiryTimesMs.removeAt(i); 406 } 407 } 408 } 409 410 /** 411 * Mark the app as used and update the bucket if necessary. If there is a expiry time specified 412 * that's in the future, then the usage event is temporary and keeps the app in the specified 413 * bucket at least until the expiry time is reached. This can be used to keep the app in an 414 * elevated bucket for a while until some important task gets to run. 415 * 416 * @param packageName package name of the app the usage is reported for 417 * @param userId user that the app is running in 418 * @param newBucket the bucket to set the app to 419 * @param usageReason sub reason for usage 420 * @param nowElapsedRealtimeMs mark as used time if non-zero (in 421 * {@link SystemClock#elapsedRealtime()} time base). 422 * @param expiryElapsedRealtimeMs the expiry time for the specified bucket (in 423 * {@link SystemClock#elapsedRealtime()} time base). 424 * @return the {@link AppUsageHistory} corresponding to the {@code packageName} 425 * and {@code userId}. 426 */ reportUsage(String packageName, int userId, int newBucket, int usageReason, long nowElapsedRealtimeMs, long expiryElapsedRealtimeMs)427 public AppUsageHistory reportUsage(String packageName, int userId, int newBucket, 428 int usageReason, long nowElapsedRealtimeMs, long expiryElapsedRealtimeMs) { 429 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 430 AppUsageHistory history = getPackageHistory(userHistory, packageName, 431 nowElapsedRealtimeMs, true); 432 return reportUsage(history, packageName, userId, newBucket, usageReason, 433 nowElapsedRealtimeMs, expiryElapsedRealtimeMs); 434 } 435 getUserHistory(int userId)436 private ArrayMap<String, AppUsageHistory> getUserHistory(int userId) { 437 ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId); 438 if (userHistory == null) { 439 userHistory = new ArrayMap<>(); 440 mIdleHistory.put(userId, userHistory); 441 readAppIdleTimes(userId, userHistory); 442 } 443 return userHistory; 444 } 445 446 // TODO (206518483): Remove unused parameter 'elapsedRealtime'. getPackageHistory(ArrayMap<String, AppUsageHistory> userHistory, String packageName, long elapsedRealtime, boolean create)447 private AppUsageHistory getPackageHistory(ArrayMap<String, AppUsageHistory> userHistory, 448 String packageName, long elapsedRealtime, boolean create) { 449 AppUsageHistory appUsageHistory = userHistory.get(packageName); 450 if (appUsageHistory == null && create) { 451 appUsageHistory = new AppUsageHistory(); 452 appUsageHistory.lastUsedByUserElapsedTime = Integer.MIN_VALUE; 453 appUsageHistory.lastUsedElapsedTime = Integer.MIN_VALUE; 454 appUsageHistory.lastUsedScreenTime = Integer.MIN_VALUE; 455 appUsageHistory.lastPredictedTime = Integer.MIN_VALUE; 456 appUsageHistory.currentBucket = STANDBY_BUCKET_NEVER; 457 appUsageHistory.bucketingReason = REASON_MAIN_DEFAULT; 458 appUsageHistory.lastInformedBucket = -1; 459 appUsageHistory.lastJobRunTime = Long.MIN_VALUE; // long long time ago 460 userHistory.put(packageName, appUsageHistory); 461 } 462 return appUsageHistory; 463 } 464 onUserRemoved(int userId)465 public void onUserRemoved(int userId) { 466 mIdleHistory.remove(userId); 467 } 468 isIdle(String packageName, int userId, long elapsedRealtime)469 public boolean isIdle(String packageName, int userId, long elapsedRealtime) { 470 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 471 AppUsageHistory appUsageHistory = 472 getPackageHistory(userHistory, packageName, elapsedRealtime, true); 473 return appUsageHistory.currentBucket >= IDLE_BUCKET_CUTOFF; 474 } 475 getAppUsageHistory(String packageName, int userId, long elapsedRealtime)476 public AppUsageHistory getAppUsageHistory(String packageName, int userId, 477 long elapsedRealtime) { 478 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 479 AppUsageHistory appUsageHistory = 480 getPackageHistory(userHistory, packageName, elapsedRealtime, true); 481 return appUsageHistory; 482 } 483 setAppStandbyBucket(String packageName, int userId, long elapsedRealtime, int bucket, int reason)484 public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime, 485 int bucket, int reason) { 486 setAppStandbyBucket(packageName, userId, elapsedRealtime, bucket, reason, false); 487 } 488 setAppStandbyBucket(String packageName, int userId, long elapsedRealtime, int bucket, int reason, boolean resetExpiryTimes)489 public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime, 490 int bucket, int reason, boolean resetExpiryTimes) { 491 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 492 AppUsageHistory appUsageHistory = 493 getPackageHistory(userHistory, packageName, elapsedRealtime, true); 494 final boolean changed = appUsageHistory.currentBucket != bucket; 495 appUsageHistory.currentBucket = bucket; 496 appUsageHistory.bucketingReason = reason; 497 498 final long elapsed = getElapsedTime(elapsedRealtime); 499 500 if ((reason & REASON_MAIN_MASK) == REASON_MAIN_PREDICTED) { 501 appUsageHistory.lastPredictedTime = elapsed; 502 appUsageHistory.lastPredictedBucket = bucket; 503 } 504 if (resetExpiryTimes && appUsageHistory.bucketExpiryTimesMs != null) { 505 appUsageHistory.bucketExpiryTimesMs.clear(); 506 } 507 if (changed) { 508 logAppStandbyBucketChanged(packageName, userId, bucket, reason); 509 } 510 } 511 512 /** 513 * Update the prediction for the app but don't change the actual bucket 514 * @param app The app for which the prediction was made 515 * @param elapsedTimeAdjusted The elapsed time in the elapsed duration timebase 516 * @param bucket The predicted bucket 517 */ updateLastPrediction(AppUsageHistory app, long elapsedTimeAdjusted, int bucket)518 public void updateLastPrediction(AppUsageHistory app, long elapsedTimeAdjusted, int bucket) { 519 app.lastPredictedTime = elapsedTimeAdjusted; 520 app.lastPredictedBucket = bucket; 521 } 522 523 /** 524 * Marks the next time the app is expected to be launched, in the current millis timebase. 525 */ setEstimatedLaunchTime(String packageName, int userId, @ElapsedRealtimeLong long nowElapsed, @CurrentTimeMillisLong long launchTime)526 public void setEstimatedLaunchTime(String packageName, int userId, 527 @ElapsedRealtimeLong long nowElapsed, @CurrentTimeMillisLong long launchTime) { 528 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 529 AppUsageHistory appUsageHistory = 530 getPackageHistory(userHistory, packageName, nowElapsed, true); 531 appUsageHistory.nextEstimatedLaunchTime = launchTime; 532 } 533 534 /** 535 * Marks the last time a job was run, with the given elapsedRealtime. The time stored is 536 * based on the elapsed timebase. 537 * @param packageName 538 * @param userId 539 * @param elapsedRealtime 540 */ setLastJobRunTime(String packageName, int userId, long elapsedRealtime)541 public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) { 542 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 543 AppUsageHistory appUsageHistory = 544 getPackageHistory(userHistory, packageName, elapsedRealtime, true); 545 appUsageHistory.lastJobRunTime = getElapsedTime(elapsedRealtime); 546 } 547 548 /** 549 * Notes an attempt to put the app in the {@link UsageStatsManager#STANDBY_BUCKET_RESTRICTED} 550 * bucket. 551 * 552 * @param packageName The package name of the app that is being restricted 553 * @param userId The ID of the user in which the app is being restricted 554 * @param elapsedRealtime The time the attempt was made, in the (unadjusted) elapsed realtime 555 * timebase 556 * @param reason The reason for the restriction attempt 557 */ noteRestrictionAttempt(String packageName, int userId, long elapsedRealtime, int reason)558 void noteRestrictionAttempt(String packageName, int userId, long elapsedRealtime, int reason) { 559 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 560 AppUsageHistory appUsageHistory = 561 getPackageHistory(userHistory, packageName, elapsedRealtime, true); 562 appUsageHistory.lastRestrictAttemptElapsedTime = getElapsedTime(elapsedRealtime); 563 appUsageHistory.lastRestrictReason = reason; 564 } 565 566 /** 567 * Returns the next estimated launch time of this app. Will return {@link Long#MAX_VALUE} if 568 * there's no estimated time. 569 */ 570 @CurrentTimeMillisLong getEstimatedLaunchTime(String packageName, int userId, long nowElapsed)571 public long getEstimatedLaunchTime(String packageName, int userId, long nowElapsed) { 572 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 573 AppUsageHistory appUsageHistory = 574 getPackageHistory(userHistory, packageName, nowElapsed, false); 575 // Don't adjust the default, else it'll wrap around to a positive value 576 if (appUsageHistory == null 577 || appUsageHistory.nextEstimatedLaunchTime < System.currentTimeMillis()) { 578 return Long.MAX_VALUE; 579 } 580 return appUsageHistory.nextEstimatedLaunchTime; 581 } 582 583 /** 584 * Returns the time since the last job was run for this app. This can be larger than the 585 * current elapsedRealtime, in case it happened before boot or a really large value if no jobs 586 * were ever run. 587 * @param packageName 588 * @param userId 589 * @param elapsedRealtime 590 * @return 591 */ getTimeSinceLastJobRun(String packageName, int userId, long elapsedRealtime)592 public long getTimeSinceLastJobRun(String packageName, int userId, long elapsedRealtime) { 593 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 594 AppUsageHistory appUsageHistory = 595 getPackageHistory(userHistory, packageName, elapsedRealtime, false); 596 // Don't adjust the default, else it'll wrap around to a positive value 597 if (appUsageHistory == null || appUsageHistory.lastJobRunTime == Long.MIN_VALUE) { 598 return Long.MAX_VALUE; 599 } 600 return getElapsedTime(elapsedRealtime) - appUsageHistory.lastJobRunTime; 601 } 602 getTimeSinceLastUsedByUser(String packageName, int userId, long elapsedRealtime)603 public long getTimeSinceLastUsedByUser(String packageName, int userId, long elapsedRealtime) { 604 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 605 AppUsageHistory appUsageHistory = 606 getPackageHistory(userHistory, packageName, elapsedRealtime, false); 607 if (appUsageHistory == null || appUsageHistory.lastUsedByUserElapsedTime == Long.MIN_VALUE 608 || appUsageHistory.lastUsedByUserElapsedTime <= 0) { 609 return Long.MAX_VALUE; 610 } 611 return getElapsedTime(elapsedRealtime) - appUsageHistory.lastUsedByUserElapsedTime; 612 } 613 getAppStandbyBucket(String packageName, int userId, long elapsedRealtime)614 public int getAppStandbyBucket(String packageName, int userId, long elapsedRealtime) { 615 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 616 AppUsageHistory appUsageHistory = 617 getPackageHistory(userHistory, packageName, elapsedRealtime, false); 618 return appUsageHistory == null ? STANDBY_BUCKET_NEVER : appUsageHistory.currentBucket; 619 } 620 getAppStandbyBuckets(int userId, boolean appIdleEnabled)621 public ArrayList<AppStandbyInfo> getAppStandbyBuckets(int userId, boolean appIdleEnabled) { 622 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 623 int size = userHistory.size(); 624 ArrayList<AppStandbyInfo> buckets = new ArrayList<>(size); 625 for (int i = 0; i < size; i++) { 626 buckets.add(new AppStandbyInfo(userHistory.keyAt(i), 627 appIdleEnabled ? userHistory.valueAt(i).currentBucket : STANDBY_BUCKET_ACTIVE)); 628 } 629 return buckets; 630 } 631 getAppStandbyReason(String packageName, int userId, long elapsedRealtime)632 public int getAppStandbyReason(String packageName, int userId, long elapsedRealtime) { 633 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 634 AppUsageHistory appUsageHistory = 635 getPackageHistory(userHistory, packageName, elapsedRealtime, false); 636 return appUsageHistory != null ? appUsageHistory.bucketingReason : 0; 637 } 638 getElapsedTime(long elapsedRealtime)639 public long getElapsedTime(long elapsedRealtime) { 640 return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration); 641 } 642 643 /* Returns the new standby bucket the app is assigned to */ setIdle(String packageName, int userId, boolean idle, long elapsedRealtime)644 public int setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) { 645 final int newBucket; 646 final int reason; 647 if (idle) { 648 newBucket = IDLE_BUCKET_CUTOFF; 649 reason = REASON_MAIN_FORCED_BY_USER; 650 final AppUsageHistory appHistory = getAppUsageHistory(packageName, userId, 651 elapsedRealtime); 652 // Wipe all expiry times that could raise the bucket on reevaluation. 653 if (appHistory.bucketExpiryTimesMs != null) { 654 for (int i = appHistory.bucketExpiryTimesMs.size() - 1; i >= 0; --i) { 655 if (appHistory.bucketExpiryTimesMs.keyAt(i) < newBucket) { 656 appHistory.bucketExpiryTimesMs.removeAt(i); 657 } 658 } 659 } 660 } else { 661 newBucket = STANDBY_BUCKET_ACTIVE; 662 // This is to pretend that the app was just used, don't freeze the state anymore. 663 reason = REASON_MAIN_USAGE | REASON_SUB_USAGE_USER_INTERACTION; 664 } 665 setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, reason, false); 666 667 return newBucket; 668 } 669 clearUsage(String packageName, int userId)670 public void clearUsage(String packageName, int userId) { 671 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 672 userHistory.remove(packageName); 673 } 674 shouldInformListeners(String packageName, int userId, long elapsedRealtime, int bucket)675 boolean shouldInformListeners(String packageName, int userId, 676 long elapsedRealtime, int bucket) { 677 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 678 AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, 679 elapsedRealtime, true); 680 if (appUsageHistory.lastInformedBucket != bucket) { 681 appUsageHistory.lastInformedBucket = bucket; 682 return true; 683 } 684 return false; 685 } 686 687 /** 688 * Returns the index in the array of elapsedTimeThresholds that corresponds to 689 * how long since the app was used. 690 * @param packageName 691 * @param userId 692 * @param elapsedRealtime current time 693 * @param screenTimeThresholds Array of screen times, in ascending order, 694 * first one is 0 (this will not be used any more) 695 * @param elapsedTimeThresholds Array of elapsed time, in ascending order, first one is 0 696 * @return The index whose values the app's used time exceeds or {@code -1} to 697 * indicate that the app has never been used. 698 */ getThresholdIndex(String packageName, int userId, long elapsedRealtime, long[] screenTimeThresholds, long[] elapsedTimeThresholds)699 int getThresholdIndex(String packageName, int userId, long elapsedRealtime, 700 long[] screenTimeThresholds, long[] elapsedTimeThresholds) { 701 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 702 AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, 703 elapsedRealtime, false); 704 // If we don't have any state for the app, assume never used 705 if (appUsageHistory == null || appUsageHistory.lastUsedElapsedTime < 0 706 || (!Flags.screenTimeBypass() && appUsageHistory.lastUsedScreenTime < 0)) { 707 return -1; 708 } 709 710 long screenOnDelta = getScreenOnTime(elapsedRealtime) - appUsageHistory.lastUsedScreenTime; 711 long elapsedDelta = getElapsedTime(elapsedRealtime) - appUsageHistory.lastUsedElapsedTime; 712 713 if (DEBUG) Slog.d(TAG, packageName 714 + " lastUsedScreen=" + appUsageHistory.lastUsedScreenTime 715 + " lastUsedElapsed=" + appUsageHistory.lastUsedElapsedTime); 716 if (DEBUG) Slog.d(TAG, packageName + " screenOn=" + screenOnDelta 717 + ", elapsed=" + elapsedDelta); 718 for (int i = screenTimeThresholds.length - 1; i >= 0; i--) { 719 if ((Flags.screenTimeBypass() || screenOnDelta >= screenTimeThresholds[i]) 720 && elapsedDelta >= elapsedTimeThresholds[i]) { 721 return i; 722 } 723 } 724 return 0; 725 } 726 727 /** 728 * Log a standby bucket change to statsd, and also logcat if debug logging is enabled. 729 */ logAppStandbyBucketChanged(String packageName, int userId, int bucket, int reason)730 private void logAppStandbyBucketChanged(String packageName, int userId, int bucket, 731 int reason) { 732 FrameworkStatsLog.write( 733 FrameworkStatsLog.APP_STANDBY_BUCKET_CHANGED, 734 packageName, userId, bucket, 735 (reason & REASON_MAIN_MASK), (reason & REASON_SUB_MASK)); 736 if (DEBUG) { 737 Slog.d(TAG, "Moved " + packageName + " to bucket=" + bucket 738 + ", reason=0x0" + Integer.toHexString(reason)); 739 } 740 } 741 742 @VisibleForTesting getBucketExpiryTimeMs(String packageName, int userId, int bucket, long elapsedRealtimeMs)743 long getBucketExpiryTimeMs(String packageName, int userId, int bucket, long elapsedRealtimeMs) { 744 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 745 AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, 746 elapsedRealtimeMs, false /* create */); 747 if (appUsageHistory == null || appUsageHistory.bucketExpiryTimesMs == null) { 748 return 0; 749 } 750 return appUsageHistory.bucketExpiryTimesMs.get(bucket, 0); 751 } 752 getUserDirectory(int userId)753 private File getUserDirectory(int userId) { 754 return new File(new File(mStorageDir, "users"), Integer.toString(userId)); 755 } 756 757 @VisibleForTesting getUserFile(int userId)758 File getUserFile(int userId) { 759 return new File(getUserDirectory(userId), APP_IDLE_FILENAME); 760 } 761 clearLastUsedTimestamps(String packageName, int userId)762 void clearLastUsedTimestamps(String packageName, int userId) { 763 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); 764 AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, 765 SystemClock.elapsedRealtime(), false /* create */); 766 if (appUsageHistory != null) { 767 appUsageHistory.lastUsedByUserElapsedTime = Integer.MIN_VALUE; 768 appUsageHistory.lastUsedElapsedTime = Integer.MIN_VALUE; 769 appUsageHistory.lastUsedScreenTime = Integer.MIN_VALUE; 770 } 771 } 772 773 /** 774 * Check if App Idle File exists on disk 775 * @param userId 776 * @return true if file exists 777 */ userFileExists(int userId)778 public boolean userFileExists(int userId) { 779 return getUserFile(userId).exists(); 780 } 781 readAppIdleTimes(int userId, ArrayMap<String, AppUsageHistory> userHistory)782 private void readAppIdleTimes(int userId, ArrayMap<String, AppUsageHistory> userHistory) { 783 FileInputStream fis = null; 784 try { 785 AtomicFile appIdleFile = new AtomicFile(getUserFile(userId)); 786 fis = appIdleFile.openRead(); 787 XmlPullParser parser = Xml.newPullParser(); 788 parser.setInput(fis, StandardCharsets.UTF_8.name()); 789 790 int type; 791 while ((type = parser.next()) != XmlPullParser.START_TAG 792 && type != XmlPullParser.END_DOCUMENT) { 793 // Skip 794 } 795 796 if (type != XmlPullParser.START_TAG) { 797 Slog.e(TAG, "Unable to read app idle file for user " + userId); 798 return; 799 } 800 if (!parser.getName().equals(TAG_PACKAGES)) { 801 return; 802 } 803 final int version = getIntValue(parser, ATTR_VERSION, XML_VERSION_INITIAL); 804 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 805 if (type == XmlPullParser.START_TAG) { 806 final String name = parser.getName(); 807 if (name.equals(TAG_PACKAGE)) { 808 final String packageName = parser.getAttributeValue(null, ATTR_NAME); 809 AppUsageHistory appUsageHistory = new AppUsageHistory(); 810 appUsageHistory.lastUsedElapsedTime = 811 Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE)); 812 appUsageHistory.lastUsedByUserElapsedTime = getLongValue(parser, 813 ATTR_LAST_USED_BY_USER_ELAPSED, 814 appUsageHistory.lastUsedElapsedTime); 815 appUsageHistory.lastUsedScreenTime = 816 Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE)); 817 appUsageHistory.lastPredictedTime = getLongValue(parser, 818 ATTR_LAST_PREDICTED_TIME, 0L); 819 String currentBucketString = parser.getAttributeValue(null, 820 ATTR_CURRENT_BUCKET); 821 appUsageHistory.currentBucket = currentBucketString == null 822 ? STANDBY_BUCKET_ACTIVE 823 : Integer.parseInt(currentBucketString); 824 String bucketingReason = 825 parser.getAttributeValue(null, ATTR_BUCKETING_REASON); 826 appUsageHistory.lastJobRunTime = getLongValue(parser, 827 ATTR_LAST_RUN_JOB_TIME, Long.MIN_VALUE); 828 appUsageHistory.bucketingReason = REASON_MAIN_DEFAULT; 829 if (bucketingReason != null) { 830 try { 831 appUsageHistory.bucketingReason = 832 Integer.parseInt(bucketingReason, 16); 833 } catch (NumberFormatException nfe) { 834 Slog.wtf(TAG, "Unable to read bucketing reason", nfe); 835 } 836 } 837 appUsageHistory.lastRestrictAttemptElapsedTime = 838 getLongValue(parser, ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED, 0); 839 String lastRestrictReason = parser.getAttributeValue( 840 null, ATTR_LAST_RESTRICTION_ATTEMPT_REASON); 841 if (lastRestrictReason != null) { 842 try { 843 appUsageHistory.lastRestrictReason = 844 Integer.parseInt(lastRestrictReason, 16); 845 } catch (NumberFormatException nfe) { 846 Slog.wtf(TAG, "Unable to read last restrict reason", nfe); 847 } 848 } 849 appUsageHistory.nextEstimatedLaunchTime = getLongValue(parser, 850 ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME, 0); 851 if (Flags.avoidIdleCheck()) { 852 // Set lastInformedBucket to the same value with the currentBucket 853 // it should have already been informed. 854 appUsageHistory.lastInformedBucket = appUsageHistory.currentBucket; 855 } else { 856 appUsageHistory.lastInformedBucket = -1; 857 } 858 userHistory.put(packageName, appUsageHistory); 859 860 if (version >= XML_VERSION_ADD_BUCKET_EXPIRY_TIMES) { 861 final int outerDepth = parser.getDepth(); 862 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 863 if (TAG_BUCKET_EXPIRY_TIMES.equals(parser.getName())) { 864 readBucketExpiryTimes(parser, appUsageHistory); 865 } 866 } 867 } else { 868 final long bucketActiveTimeoutTime = getLongValue(parser, 869 ATTR_BUCKET_ACTIVE_TIMEOUT_TIME, 0L); 870 final long bucketWorkingSetTimeoutTime = getLongValue(parser, 871 ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME, 0L); 872 if (bucketActiveTimeoutTime != 0 || bucketWorkingSetTimeoutTime != 0) { 873 insertBucketExpiryTime(appUsageHistory, 874 STANDBY_BUCKET_ACTIVE, bucketActiveTimeoutTime); 875 insertBucketExpiryTime(appUsageHistory, 876 STANDBY_BUCKET_WORKING_SET, bucketWorkingSetTimeoutTime); 877 } 878 } 879 } 880 } 881 } 882 } catch (FileNotFoundException e) { 883 // Expected on first boot 884 Slog.d(TAG, "App idle file for user " + userId + " does not exist"); 885 } catch (IOException | XmlPullParserException e) { 886 Slog.e(TAG, "Unable to read app idle file for user " + userId, e); 887 } finally { 888 IoUtils.closeQuietly(fis); 889 } 890 } 891 readBucketExpiryTimes(XmlPullParser parser, AppUsageHistory appUsageHistory)892 private void readBucketExpiryTimes(XmlPullParser parser, AppUsageHistory appUsageHistory) 893 throws IOException, XmlPullParserException { 894 final int depth = parser.getDepth(); 895 while (XmlUtils.nextElementWithin(parser, depth)) { 896 if (TAG_ITEM.equals(parser.getName())) { 897 final int bucket = getIntValue(parser, ATTR_BUCKET, STANDBY_BUCKET_UNKNOWN); 898 if (bucket == STANDBY_BUCKET_UNKNOWN) { 899 Slog.e(TAG, "Error reading the buckets expiry times"); 900 continue; 901 } 902 final long expiryTimeMs = getLongValue(parser, ATTR_EXPIRY_TIME, 0 /* default */); 903 insertBucketExpiryTime(appUsageHistory, bucket, expiryTimeMs); 904 } 905 } 906 } 907 insertBucketExpiryTime(AppUsageHistory appUsageHistory, int bucket, long expiryTimeMs)908 private void insertBucketExpiryTime(AppUsageHistory appUsageHistory, 909 int bucket, long expiryTimeMs) { 910 if (expiryTimeMs == 0) { 911 return; 912 } 913 if (appUsageHistory.bucketExpiryTimesMs == null) { 914 appUsageHistory.bucketExpiryTimesMs = new SparseLongArray(); 915 } 916 appUsageHistory.bucketExpiryTimesMs.put(bucket, expiryTimeMs); 917 } 918 getLongValue(XmlPullParser parser, String attrName, long defValue)919 private long getLongValue(XmlPullParser parser, String attrName, long defValue) { 920 String value = parser.getAttributeValue(null, attrName); 921 if (value == null) return defValue; 922 return Long.parseLong(value); 923 } 924 getIntValue(XmlPullParser parser, String attrName, int defValue)925 private int getIntValue(XmlPullParser parser, String attrName, int defValue) { 926 String value = parser.getAttributeValue(null, attrName); 927 if (value == null) return defValue; 928 return Integer.parseInt(value); 929 } 930 writeAppIdleTimes(long elapsedRealtimeMs)931 public void writeAppIdleTimes(long elapsedRealtimeMs) { 932 final int size = mIdleHistory.size(); 933 for (int i = 0; i < size; i++) { 934 writeAppIdleTimes(mIdleHistory.keyAt(i), elapsedRealtimeMs); 935 } 936 } 937 writeAppIdleTimes(int userId, long elapsedRealtimeMs)938 public void writeAppIdleTimes(int userId, long elapsedRealtimeMs) { 939 FileOutputStream fos = null; 940 AtomicFile appIdleFile = new AtomicFile(getUserFile(userId)); 941 try { 942 fos = appIdleFile.startWrite(); 943 final BufferedOutputStream bos = new BufferedOutputStream(fos); 944 945 FastXmlSerializer xml = new FastXmlSerializer(); 946 xml.setOutput(bos, StandardCharsets.UTF_8.name()); 947 xml.startDocument(null, true); 948 xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 949 950 xml.startTag(null, TAG_PACKAGES); 951 xml.attribute(null, ATTR_VERSION, String.valueOf(XML_VERSION_CURRENT)); 952 953 final long elapsedTimeMs = getElapsedTime(elapsedRealtimeMs); 954 ArrayMap<String,AppUsageHistory> userHistory = getUserHistory(userId); 955 final int N = userHistory.size(); 956 for (int i = 0; i < N; i++) { 957 String packageName = userHistory.keyAt(i); 958 // Skip any unexpected null package names 959 if (packageName == null) { 960 Slog.w(TAG, "Skipping App Idle write for unexpected null package"); 961 continue; 962 } 963 AppUsageHistory history = userHistory.valueAt(i); 964 xml.startTag(null, TAG_PACKAGE); 965 xml.attribute(null, ATTR_NAME, packageName); 966 xml.attribute(null, ATTR_ELAPSED_IDLE, 967 Long.toString(history.lastUsedElapsedTime)); 968 xml.attribute(null, ATTR_LAST_USED_BY_USER_ELAPSED, 969 Long.toString(history.lastUsedByUserElapsedTime)); 970 xml.attribute(null, ATTR_SCREEN_IDLE, 971 Long.toString(history.lastUsedScreenTime)); 972 xml.attribute(null, ATTR_LAST_PREDICTED_TIME, 973 Long.toString(history.lastPredictedTime)); 974 xml.attribute(null, ATTR_CURRENT_BUCKET, 975 Integer.toString(history.currentBucket)); 976 xml.attribute(null, ATTR_BUCKETING_REASON, 977 Integer.toHexString(history.bucketingReason)); 978 if (history.lastJobRunTime != Long.MIN_VALUE) { 979 xml.attribute(null, ATTR_LAST_RUN_JOB_TIME, Long.toString(history 980 .lastJobRunTime)); 981 } 982 if (history.lastRestrictAttemptElapsedTime > 0) { 983 xml.attribute(null, ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED, 984 Long.toString(history.lastRestrictAttemptElapsedTime)); 985 } 986 xml.attribute(null, ATTR_LAST_RESTRICTION_ATTEMPT_REASON, 987 Integer.toHexString(history.lastRestrictReason)); 988 if (history.nextEstimatedLaunchTime > 0) { 989 xml.attribute(null, ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME, 990 Long.toString(history.nextEstimatedLaunchTime)); 991 } 992 if (history.bucketExpiryTimesMs != null) { 993 xml.startTag(null, TAG_BUCKET_EXPIRY_TIMES); 994 final int size = history.bucketExpiryTimesMs.size(); 995 for (int j = 0; j < size; ++j) { 996 final long expiryTimeMs = history.bucketExpiryTimesMs.valueAt(j); 997 // Skip writing to disk if the expiry time already elapsed. 998 if (expiryTimeMs < elapsedTimeMs) { 999 continue; 1000 } 1001 final int bucket = history.bucketExpiryTimesMs.keyAt(j); 1002 xml.startTag(null, TAG_ITEM); 1003 xml.attribute(null, ATTR_BUCKET, String.valueOf(bucket)); 1004 xml.attribute(null, ATTR_EXPIRY_TIME, String.valueOf(expiryTimeMs)); 1005 xml.endTag(null, TAG_ITEM); 1006 } 1007 xml.endTag(null, TAG_BUCKET_EXPIRY_TIMES); 1008 } 1009 xml.endTag(null, TAG_PACKAGE); 1010 } 1011 1012 xml.endTag(null, TAG_PACKAGES); 1013 xml.endDocument(); 1014 appIdleFile.finishWrite(fos); 1015 } catch (Exception e) { 1016 appIdleFile.failWrite(fos); 1017 Slog.e(TAG, "Error writing app idle file for user " + userId, e); 1018 } 1019 } 1020 dumpUsers(IndentingPrintWriter idpw, int[] userIds, List<String> pkgs)1021 public void dumpUsers(IndentingPrintWriter idpw, int[] userIds, List<String> pkgs) { 1022 final int numUsers = userIds.length; 1023 for (int i = 0; i < numUsers; i++) { 1024 idpw.println(); 1025 dumpUser(idpw, userIds[i], pkgs); 1026 } 1027 } 1028 dumpUser(IndentingPrintWriter idpw, int userId, List<String> pkgs)1029 private void dumpUser(IndentingPrintWriter idpw, int userId, List<String> pkgs) { 1030 idpw.print("User "); 1031 idpw.print(userId); 1032 idpw.println(" App Standby States:"); 1033 idpw.increaseIndent(); 1034 ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId); 1035 final long now = System.currentTimeMillis(); 1036 final long elapsedRealtime = SystemClock.elapsedRealtime(); 1037 final long totalElapsedTime = getElapsedTime(elapsedRealtime); 1038 final long screenOnTime = getScreenOnTime(elapsedRealtime); 1039 if (userHistory == null) return; 1040 final int P = userHistory.size(); 1041 for (int p = 0; p < P; p++) { 1042 final String packageName = userHistory.keyAt(p); 1043 final AppUsageHistory appUsageHistory = userHistory.valueAt(p); 1044 if (!CollectionUtils.isEmpty(pkgs) && !pkgs.contains(packageName)) { 1045 continue; 1046 } 1047 idpw.print("package=" + packageName); 1048 idpw.print(" u=" + userId); 1049 idpw.print(" bucket=" + appUsageHistory.currentBucket 1050 + " reason=" 1051 + UsageStatsManager.reasonToString(appUsageHistory.bucketingReason)); 1052 idpw.print(" used="); 1053 printLastActionElapsedTime(idpw, totalElapsedTime, appUsageHistory.lastUsedElapsedTime); 1054 idpw.print(" usedByUser="); 1055 printLastActionElapsedTime(idpw, totalElapsedTime, 1056 appUsageHistory.lastUsedByUserElapsedTime); 1057 idpw.print(" usedScr="); 1058 printLastActionElapsedTime(idpw, totalElapsedTime, appUsageHistory.lastUsedScreenTime); 1059 idpw.print(" lastPred="); 1060 printLastActionElapsedTime(idpw, totalElapsedTime, appUsageHistory.lastPredictedTime); 1061 dumpBucketExpiryTimes(idpw, appUsageHistory, totalElapsedTime); 1062 idpw.print(" lastJob="); 1063 TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw); 1064 idpw.print(" lastInformedBucket=" + appUsageHistory.lastInformedBucket); 1065 if (appUsageHistory.lastRestrictAttemptElapsedTime > 0) { 1066 idpw.print(" lastRestrictAttempt="); 1067 TimeUtils.formatDuration( 1068 totalElapsedTime - appUsageHistory.lastRestrictAttemptElapsedTime, idpw); 1069 idpw.print(" lastRestrictReason=" 1070 + UsageStatsManager.reasonToString(appUsageHistory.lastRestrictReason)); 1071 } 1072 if (appUsageHistory.nextEstimatedLaunchTime > 0) { 1073 idpw.print(" nextEstimatedLaunchTime="); 1074 TimeUtils.formatDuration(appUsageHistory.nextEstimatedLaunchTime - now, idpw); 1075 } 1076 idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n")); 1077 idpw.println(); 1078 } 1079 idpw.println(); 1080 idpw.print("totalElapsedTime="); 1081 TimeUtils.formatDuration(getElapsedTime(elapsedRealtime), idpw); 1082 idpw.println(); 1083 idpw.print("totalScreenOnTime="); 1084 TimeUtils.formatDuration(getScreenOnTime(elapsedRealtime), idpw); 1085 idpw.println(); 1086 idpw.decreaseIndent(); 1087 } 1088 printLastActionElapsedTime(IndentingPrintWriter idpw, long totalElapsedTimeMS, long lastActionTimeMs)1089 private void printLastActionElapsedTime(IndentingPrintWriter idpw, long totalElapsedTimeMS, 1090 long lastActionTimeMs) { 1091 if (lastActionTimeMs < 0) { 1092 idpw.print("<uninitialized>"); 1093 } else { 1094 TimeUtils.formatDuration(totalElapsedTimeMS - lastActionTimeMs, idpw); 1095 } 1096 } 1097 dumpBucketExpiryTimes(IndentingPrintWriter idpw, AppUsageHistory appUsageHistory, long totalElapsedTimeMs)1098 private void dumpBucketExpiryTimes(IndentingPrintWriter idpw, AppUsageHistory appUsageHistory, 1099 long totalElapsedTimeMs) { 1100 idpw.print(" expiryTimes="); 1101 if (appUsageHistory.bucketExpiryTimesMs == null 1102 || appUsageHistory.bucketExpiryTimesMs.size() == 0) { 1103 idpw.print("<none>"); 1104 return; 1105 } 1106 idpw.print("("); 1107 final int size = appUsageHistory.bucketExpiryTimesMs.size(); 1108 for (int i = 0; i < size; ++i) { 1109 final int bucket = appUsageHistory.bucketExpiryTimesMs.keyAt(i); 1110 final long expiryTimeMs = appUsageHistory.bucketExpiryTimesMs.valueAt(i); 1111 if (i != 0) { 1112 idpw.print(","); 1113 } 1114 idpw.print(bucket + ":"); 1115 TimeUtils.formatDuration(totalElapsedTimeMs - expiryTimeMs, idpw); 1116 } 1117 idpw.print(")"); 1118 } 1119 } 1120