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