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