UserUsageStatsService.java revision da4a3775735ffc0fa510031f75bc459b764b259d
1/** 2 * Copyright (C) 2014 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 17package com.android.server.usage; 18 19import android.app.usage.ConfigurationStats; 20import android.app.usage.TimeSparseArray; 21import android.app.usage.UsageEvents; 22import android.app.usage.UsageEvents.Event; 23import android.app.usage.UsageStats; 24import android.app.usage.UsageStatsManager; 25import android.content.pm.PackageInfo; 26import android.content.pm.PackageManager; 27import android.content.res.Configuration; 28import android.os.SystemClock; 29import android.content.Context; 30import android.text.format.DateUtils; 31import android.util.ArrayMap; 32import android.util.ArraySet; 33import android.util.Slog; 34 35import com.android.internal.util.IndentingPrintWriter; 36import com.android.server.usage.UsageStatsDatabase.StatCombiner; 37 38import java.io.File; 39import java.io.IOException; 40import java.text.SimpleDateFormat; 41import java.util.ArrayList; 42import java.util.Arrays; 43import java.util.List; 44 45/** 46 * A per-user UsageStatsService. All methods are meant to be called with the main lock held 47 * in UsageStatsService. 48 */ 49class UserUsageStatsService { 50 private static final String TAG = "UsageStatsService"; 51 private static final boolean DEBUG = UsageStatsService.DEBUG; 52 private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 53 private static final int sDateFormatFlags = 54 DateUtils.FORMAT_SHOW_DATE 55 | DateUtils.FORMAT_SHOW_TIME 56 | DateUtils.FORMAT_SHOW_YEAR 57 | DateUtils.FORMAT_NUMERIC_DATE; 58 59 private final Context mContext; 60 private final UsageStatsDatabase mDatabase; 61 private final IntervalStats[] mCurrentStats; 62 private IntervalStats mAppIdleRollingWindow; 63 private boolean mStatsChanged = false; 64 private final UnixCalendar mDailyExpiryDate; 65 private final StatsUpdatedListener mListener; 66 private final String mLogPrefix; 67 private final int mUserId; 68 69 interface StatsUpdatedListener { 70 void onStatsUpdated(); 71 } 72 73 UserUsageStatsService(Context context, int userId, File usageStatsDir, 74 StatsUpdatedListener listener) { 75 mContext = context; 76 mDailyExpiryDate = new UnixCalendar(0); 77 mDatabase = new UsageStatsDatabase(usageStatsDir); 78 mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT]; 79 mListener = listener; 80 mLogPrefix = "User[" + Integer.toString(userId) + "] "; 81 mUserId = userId; 82 } 83 84 void init(final long currentTimeMillis, final long deviceUsageTime) { 85 mDatabase.init(currentTimeMillis); 86 87 int nullCount = 0; 88 for (int i = 0; i < mCurrentStats.length; i++) { 89 mCurrentStats[i] = mDatabase.getLatestUsageStats(i); 90 if (mCurrentStats[i] == null) { 91 // Find out how many intervals we don't have data for. 92 // Ideally it should be all or none. 93 nullCount++; 94 } 95 } 96 97 if (nullCount > 0) { 98 if (nullCount != mCurrentStats.length) { 99 // This is weird, but we shouldn't fail if something like this 100 // happens. 101 Slog.w(TAG, mLogPrefix + "Some stats have no latest available"); 102 } else { 103 // This must be first boot. 104 } 105 106 // By calling loadActiveStats, we will 107 // generate new stats for each bucket. 108 loadActiveStats(currentTimeMillis,/*force=*/ false, /*resetBeginIdleTime=*/ false); 109 } else { 110 // Set up the expiry date to be one day from the latest daily stat. 111 // This may actually be today and we will rollover on the first event 112 // that is reported. 113 mDailyExpiryDate.setTimeInMillis( 114 mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime); 115 mDailyExpiryDate.addDays(1); 116 mDailyExpiryDate.truncateToDay(); 117 Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " + 118 sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + 119 "(" + mDailyExpiryDate.getTimeInMillis() + ")"); 120 } 121 122 // Now close off any events that were open at the time this was saved. 123 for (IntervalStats stat : mCurrentStats) { 124 final int pkgCount = stat.packageStats.size(); 125 for (int i = 0; i < pkgCount; i++) { 126 UsageStats pkgStats = stat.packageStats.valueAt(i); 127 if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND || 128 pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) { 129 stat.update(pkgStats.mPackageName, stat.lastTimeSaved, 130 UsageEvents.Event.END_OF_DAY); 131 notifyStatsChanged(); 132 } 133 } 134 135 stat.updateConfigurationStats(null, stat.lastTimeSaved); 136 } 137 138 if (mDatabase.isNewUpdate()) { 139 initializeDefaultsForApps(currentTimeMillis, deviceUsageTime, 140 mDatabase.isFirstUpdate()); 141 } 142 143 refreshAppIdleRollingWindow(currentTimeMillis); 144 } 145 146 /** 147 * If any of the apps don't have a last-used entry, add one now. 148 * @param currentTimeMillis the current time 149 * @param firstUpdate if it is the first update, touch all installed apps, otherwise only 150 * touch the system apps 151 */ 152 private void initializeDefaultsForApps(long currentTimeMillis, long deviceUsageTime, 153 boolean firstUpdate) { 154 PackageManager pm = mContext.getPackageManager(); 155 List<PackageInfo> packages = pm.getInstalledPackages(0, mUserId); 156 final int packageCount = packages.size(); 157 for (int i = 0; i < packageCount; i++) { 158 final PackageInfo pi = packages.get(i); 159 String packageName = pi.packageName; 160 if (pi.applicationInfo != null && (firstUpdate || pi.applicationInfo.isSystemApp()) 161 && getBeginIdleTime(packageName) == -1) { 162 for (IntervalStats stats : mCurrentStats) { 163 stats.update(packageName, currentTimeMillis, Event.SYSTEM_INTERACTION); 164 stats.updateBeginIdleTime(packageName, deviceUsageTime); 165 mStatsChanged = true; 166 } 167 } 168 } 169 // Persist the new OTA-related access stats. 170 persistActiveStats(); 171 } 172 173 void onTimeChanged(long oldTime, long newTime, boolean resetBeginIdleTime) { 174 persistActiveStats(); 175 mDatabase.onTimeChanged(newTime - oldTime); 176 loadActiveStats(newTime, /* force= */ true, resetBeginIdleTime); 177 refreshAppIdleRollingWindow(newTime); 178 } 179 180 void reportEvent(UsageEvents.Event event, long deviceUsageTime) { 181 if (DEBUG) { 182 Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage 183 + "[" + event.mTimeStamp + "]: " 184 + eventToString(event.mEventType)); 185 } 186 187 if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) { 188 // Need to rollover 189 rolloverStats(event.mTimeStamp); 190 } 191 192 final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY]; 193 194 final Configuration newFullConfig = event.mConfiguration; 195 if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE && 196 currentDailyStats.activeConfiguration != null) { 197 // Make the event configuration a delta. 198 event.mConfiguration = Configuration.generateDelta( 199 currentDailyStats.activeConfiguration, newFullConfig); 200 } 201 202 // Add the event to the daily list. 203 if (currentDailyStats.events == null) { 204 currentDailyStats.events = new TimeSparseArray<>(); 205 } 206 if (event.mEventType != UsageEvents.Event.SYSTEM_INTERACTION) { 207 currentDailyStats.events.put(event.mTimeStamp, event); 208 } 209 210 for (IntervalStats stats : mCurrentStats) { 211 if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) { 212 stats.updateConfigurationStats(newFullConfig, event.mTimeStamp); 213 } else { 214 stats.update(event.mPackage, event.mTimeStamp, event.mEventType); 215 stats.updateBeginIdleTime(event.mPackage, deviceUsageTime); 216 } 217 } 218 219 if (event.mEventType != Event.CONFIGURATION_CHANGE) { 220 mAppIdleRollingWindow.update(event.mPackage, event.mTimeStamp, event.mEventType); 221 mAppIdleRollingWindow.updateBeginIdleTime(event.mPackage, deviceUsageTime); 222 } 223 224 notifyStatsChanged(); 225 } 226 227 /** 228 * Sets the beginIdleTime for each of the intervals. 229 * @param beginIdleTime 230 */ 231 void setBeginIdleTime(String packageName, long beginIdleTime) { 232 for (IntervalStats stats : mCurrentStats) { 233 stats.updateBeginIdleTime(packageName, beginIdleTime); 234 } 235 mAppIdleRollingWindow.updateBeginIdleTime(packageName, beginIdleTime); 236 notifyStatsChanged(); 237 } 238 239 void setSystemLastUsedTime(String packageName, long lastUsedTime) { 240 for (IntervalStats stats : mCurrentStats) { 241 stats.updateSystemLastUsedTime(packageName, lastUsedTime); 242 } 243 mAppIdleRollingWindow.updateSystemLastUsedTime(packageName, lastUsedTime); 244 notifyStatsChanged(); 245 } 246 247 private static final StatCombiner<UsageStats> sUsageStatsCombiner = 248 new StatCombiner<UsageStats>() { 249 @Override 250 public void combine(IntervalStats stats, boolean mutable, 251 List<UsageStats> accResult) { 252 if (!mutable) { 253 accResult.addAll(stats.packageStats.values()); 254 return; 255 } 256 257 final int statCount = stats.packageStats.size(); 258 for (int i = 0; i < statCount; i++) { 259 accResult.add(new UsageStats(stats.packageStats.valueAt(i))); 260 } 261 } 262 }; 263 264 private static final StatCombiner<ConfigurationStats> sConfigStatsCombiner = 265 new StatCombiner<ConfigurationStats>() { 266 @Override 267 public void combine(IntervalStats stats, boolean mutable, 268 List<ConfigurationStats> accResult) { 269 if (!mutable) { 270 accResult.addAll(stats.configurations.values()); 271 return; 272 } 273 274 final int configCount = stats.configurations.size(); 275 for (int i = 0; i < configCount; i++) { 276 accResult.add(new ConfigurationStats(stats.configurations.valueAt(i))); 277 } 278 } 279 }; 280 281 /** 282 * Generic query method that selects the appropriate IntervalStats for the specified time range 283 * and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner} 284 * provided to select the stats to use from the IntervalStats object. 285 */ 286 private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime, 287 StatCombiner<T> combiner) { 288 if (intervalType == UsageStatsManager.INTERVAL_BEST) { 289 intervalType = mDatabase.findBestFitBucket(beginTime, endTime); 290 if (intervalType < 0) { 291 // Nothing saved to disk yet, so every stat is just as equal (no rollover has 292 // occurred. 293 intervalType = UsageStatsManager.INTERVAL_DAILY; 294 } 295 } 296 297 if (intervalType < 0 || intervalType >= mCurrentStats.length) { 298 if (DEBUG) { 299 Slog.d(TAG, mLogPrefix + "Bad intervalType used " + intervalType); 300 } 301 return null; 302 } 303 304 final IntervalStats currentStats = mCurrentStats[intervalType]; 305 306 if (DEBUG) { 307 Slog.d(TAG, mLogPrefix + "SELECT * FROM " + intervalType + " WHERE beginTime >= " 308 + beginTime + " AND endTime < " + endTime); 309 } 310 311 if (beginTime >= currentStats.endTime) { 312 if (DEBUG) { 313 Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is " 314 + currentStats.endTime); 315 } 316 // Nothing newer available. 317 return null; 318 } 319 320 // Truncate the endTime to just before the in-memory stats. Then, we'll append the 321 // in-memory stats to the results (if necessary) so as to avoid writing to disk too 322 // often. 323 final long truncatedEndTime = Math.min(currentStats.beginTime, endTime); 324 325 // Get the stats from disk. 326 List<T> results = mDatabase.queryUsageStats(intervalType, beginTime, 327 truncatedEndTime, combiner); 328 if (DEBUG) { 329 Slog.d(TAG, "Got " + (results != null ? results.size() : 0) + " results from disk"); 330 Slog.d(TAG, "Current stats beginTime=" + currentStats.beginTime + 331 " endTime=" + currentStats.endTime); 332 } 333 334 // Now check if the in-memory stats match the range and add them if they do. 335 if (beginTime < currentStats.endTime && endTime > currentStats.beginTime) { 336 if (DEBUG) { 337 Slog.d(TAG, mLogPrefix + "Returning in-memory stats"); 338 } 339 340 if (results == null) { 341 results = new ArrayList<>(); 342 } 343 combiner.combine(currentStats, true, results); 344 } 345 346 if (DEBUG) { 347 Slog.d(TAG, mLogPrefix + "Results: " + (results != null ? results.size() : 0)); 348 } 349 return results; 350 } 351 352 List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) { 353 return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner); 354 } 355 356 List<ConfigurationStats> queryConfigurationStats(int bucketType, long beginTime, long endTime) { 357 return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner); 358 } 359 360 UsageEvents queryEvents(final long beginTime, final long endTime) { 361 final ArraySet<String> names = new ArraySet<>(); 362 List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY, 363 beginTime, endTime, new StatCombiner<UsageEvents.Event>() { 364 @Override 365 public void combine(IntervalStats stats, boolean mutable, 366 List<UsageEvents.Event> accumulatedResult) { 367 if (stats.events == null) { 368 return; 369 } 370 371 final int startIndex = stats.events.closestIndexOnOrAfter(beginTime); 372 if (startIndex < 0) { 373 return; 374 } 375 376 final int size = stats.events.size(); 377 for (int i = startIndex; i < size; i++) { 378 if (stats.events.keyAt(i) >= endTime) { 379 return; 380 } 381 382 final UsageEvents.Event event = stats.events.valueAt(i); 383 names.add(event.mPackage); 384 if (event.mClass != null) { 385 names.add(event.mClass); 386 } 387 accumulatedResult.add(event); 388 } 389 } 390 }); 391 392 if (results == null || results.isEmpty()) { 393 return null; 394 } 395 396 String[] table = names.toArray(new String[names.size()]); 397 Arrays.sort(table); 398 return new UsageEvents(results, table); 399 } 400 401 long getBeginIdleTime(String packageName) { 402 UsageStats packageUsage; 403 if ((packageUsage = mAppIdleRollingWindow.packageStats.get(packageName)) == null) { 404 return -1; 405 } else { 406 return packageUsage.getBeginIdleTime(); 407 } 408 } 409 410 long getSystemLastUsedTime(String packageName) { 411 UsageStats packageUsage; 412 if ((packageUsage = mAppIdleRollingWindow.packageStats.get(packageName)) == null) { 413 return -1; 414 } else { 415 return packageUsage.getLastTimeSystemUsed(); 416 } 417 } 418 419 void persistActiveStats() { 420 if (mStatsChanged) { 421 Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk"); 422 try { 423 for (int i = 0; i < mCurrentStats.length; i++) { 424 mDatabase.putUsageStats(i, mCurrentStats[i]); 425 } 426 mStatsChanged = false; 427 } catch (IOException e) { 428 Slog.e(TAG, mLogPrefix + "Failed to persist active stats", e); 429 } 430 } 431 } 432 433 private void rolloverStats(final long currentTimeMillis) { 434 final long startTime = SystemClock.elapsedRealtime(); 435 Slog.i(TAG, mLogPrefix + "Rolling over usage stats"); 436 437 // Finish any ongoing events with an END_OF_DAY event. Make a note of which components 438 // need a new CONTINUE_PREVIOUS_DAY entry. 439 final Configuration previousConfig = 440 mCurrentStats[UsageStatsManager.INTERVAL_DAILY].activeConfiguration; 441 ArraySet<String> continuePreviousDay = new ArraySet<>(); 442 for (IntervalStats stat : mCurrentStats) { 443 final int pkgCount = stat.packageStats.size(); 444 for (int i = 0; i < pkgCount; i++) { 445 UsageStats pkgStats = stat.packageStats.valueAt(i); 446 if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND || 447 pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) { 448 continuePreviousDay.add(pkgStats.mPackageName); 449 stat.update(pkgStats.mPackageName, mDailyExpiryDate.getTimeInMillis() - 1, 450 UsageEvents.Event.END_OF_DAY); 451 notifyStatsChanged(); 452 } 453 } 454 455 stat.updateConfigurationStats(null, mDailyExpiryDate.getTimeInMillis() - 1); 456 } 457 458 persistActiveStats(); 459 mDatabase.prune(currentTimeMillis); 460 loadActiveStats(currentTimeMillis, /*force=*/ false, /*resetBeginIdleTime=*/ false); 461 462 final int continueCount = continuePreviousDay.size(); 463 for (int i = 0; i < continueCount; i++) { 464 String name = continuePreviousDay.valueAt(i); 465 final long beginTime = mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime; 466 for (IntervalStats stat : mCurrentStats) { 467 stat.update(name, beginTime, UsageEvents.Event.CONTINUE_PREVIOUS_DAY); 468 stat.updateConfigurationStats(previousConfig, beginTime); 469 notifyStatsChanged(); 470 } 471 } 472 persistActiveStats(); 473 474 refreshAppIdleRollingWindow(currentTimeMillis); 475 476 final long totalTime = SystemClock.elapsedRealtime() - startTime; 477 Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime 478 + " milliseconds"); 479 } 480 481 private void notifyStatsChanged() { 482 if (!mStatsChanged) { 483 mStatsChanged = true; 484 mListener.onStatsUpdated(); 485 } 486 } 487 488 /** 489 * @param force To force all in-memory stats to be reloaded. 490 */ 491 private void loadActiveStats(final long currentTimeMillis, boolean force, 492 boolean resetBeginIdleTime) { 493 final UnixCalendar tempCal = mDailyExpiryDate; 494 for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) { 495 tempCal.setTimeInMillis(currentTimeMillis); 496 UnixCalendar.truncateTo(tempCal, intervalType); 497 498 if (!force && mCurrentStats[intervalType] != null && 499 mCurrentStats[intervalType].beginTime == tempCal.getTimeInMillis()) { 500 // These are the same, no need to load them (in memory stats are always newer 501 // than persisted stats). 502 continue; 503 } 504 505 final long lastBeginTime = mDatabase.getLatestUsageStatsBeginTime(intervalType); 506 if (lastBeginTime >= tempCal.getTimeInMillis()) { 507 if (DEBUG) { 508 Slog.d(TAG, mLogPrefix + "Loading existing stats @ " + 509 sDateFormat.format(lastBeginTime) + "(" + lastBeginTime + 510 ") for interval " + intervalType); 511 } 512 mCurrentStats[intervalType] = mDatabase.getLatestUsageStats(intervalType); 513 } else { 514 mCurrentStats[intervalType] = null; 515 } 516 517 if (mCurrentStats[intervalType] == null) { 518 if (DEBUG) { 519 Slog.d(TAG, "Creating new stats @ " + 520 sDateFormat.format(tempCal.getTimeInMillis()) + "(" + 521 tempCal.getTimeInMillis() + ") for interval " + intervalType); 522 523 } 524 mCurrentStats[intervalType] = new IntervalStats(); 525 mCurrentStats[intervalType].beginTime = tempCal.getTimeInMillis(); 526 mCurrentStats[intervalType].endTime = currentTimeMillis; 527 } 528 529 if (resetBeginIdleTime) { 530 for (UsageStats usageStats : mCurrentStats[intervalType].packageStats.values()) { 531 usageStats.mBeginIdleTime = 0; 532 } 533 } 534 } 535 536 mStatsChanged = false; 537 mDailyExpiryDate.setTimeInMillis(currentTimeMillis); 538 mDailyExpiryDate.addDays(1); 539 mDailyExpiryDate.truncateToDay(); 540 Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " + 541 sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" + 542 tempCal.getTimeInMillis() + ")"); 543 } 544 545 private static void mergePackageStats(IntervalStats dst, IntervalStats src) { 546 dst.endTime = Math.max(dst.endTime, src.endTime); 547 548 final int srcPackageCount = src.packageStats.size(); 549 for (int i = 0; i < srcPackageCount; i++) { 550 final String packageName = src.packageStats.keyAt(i); 551 final UsageStats srcStats = src.packageStats.valueAt(i); 552 final UsageStats dstStats = dst.packageStats.get(packageName); 553 if (dstStats == null) { 554 dst.packageStats.put(packageName, new UsageStats(srcStats)); 555 } else { 556 dstStats.add(src.packageStats.valueAt(i)); 557 } 558 } 559 } 560 561 /** 562 * Merges all the stats into the first element of the resulting list. 563 */ 564 private static final StatCombiner<IntervalStats> sPackageStatsMerger = 565 new StatCombiner<IntervalStats>() { 566 @Override 567 public void combine(IntervalStats stats, boolean mutable, 568 List<IntervalStats> accumulatedResult) { 569 IntervalStats accum; 570 if (accumulatedResult.isEmpty()) { 571 accum = new IntervalStats(); 572 accum.beginTime = stats.beginTime; 573 accumulatedResult.add(accum); 574 } else { 575 accum = accumulatedResult.get(0); 576 } 577 578 mergePackageStats(accum, stats); 579 } 580 }; 581 582 /** 583 * App idle operates on a rolling window of time. When we roll over time, we end up with a 584 * period of time where in-memory stats are empty and we don't hit the disk for older stats 585 * for performance reasons. Suddenly all apps will become idle. 586 * 587 * Instead, at times we do a deep query to find all the apps that have run in the past few 588 * days and keep the cached data up to date. 589 * 590 * @param currentTimeMillis 591 */ 592 void refreshAppIdleRollingWindow(long currentTimeMillis) { 593 // Start the rolling window for AppIdle requests. 594 List<IntervalStats> stats = mDatabase.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, 595 currentTimeMillis - (1000 * 60 * 60 * 24 * 2), currentTimeMillis, 596 sPackageStatsMerger); 597 598 if (stats == null || stats.isEmpty()) { 599 mAppIdleRollingWindow = new IntervalStats(); 600 mergePackageStats(mAppIdleRollingWindow, 601 mCurrentStats[UsageStatsManager.INTERVAL_YEARLY]); 602 } else { 603 mAppIdleRollingWindow = stats.get(0); 604 } 605 } 606 607 // 608 // -- DUMP related methods -- 609 // 610 611 void checkin(final IndentingPrintWriter pw, final long screenOnTime) { 612 mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() { 613 @Override 614 public boolean checkin(IntervalStats stats) { 615 printIntervalStats(pw, stats, screenOnTime, false); 616 return true; 617 } 618 }); 619 } 620 621 void dump(IndentingPrintWriter pw, final long screenOnTime) { 622 // This is not a check-in, only dump in-memory stats. 623 for (int interval = 0; interval < mCurrentStats.length; interval++) { 624 pw.print("In-memory "); 625 pw.print(intervalToString(interval)); 626 pw.println(" stats"); 627 printIntervalStats(pw, mCurrentStats[interval], screenOnTime, true); 628 } 629 630 pw.println("AppIdleRollingWindow cache"); 631 printIntervalStats(pw, mAppIdleRollingWindow, screenOnTime, true); 632 } 633 634 private String formatDateTime(long dateTime, boolean pretty) { 635 if (pretty) { 636 return "\"" + DateUtils.formatDateTime(mContext, dateTime, sDateFormatFlags) + "\""; 637 } 638 return Long.toString(dateTime); 639 } 640 641 private String formatElapsedTime(long elapsedTime, boolean pretty) { 642 if (pretty) { 643 return "\"" + DateUtils.formatElapsedTime(elapsedTime / 1000) + "\""; 644 } 645 return Long.toString(elapsedTime); 646 } 647 648 void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, long screenOnTime, 649 boolean prettyDates) { 650 if (prettyDates) { 651 pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext, 652 stats.beginTime, stats.endTime, sDateFormatFlags) + "\""); 653 } else { 654 pw.printPair("beginTime", stats.beginTime); 655 pw.printPair("endTime", stats.endTime); 656 } 657 pw.println(); 658 pw.increaseIndent(); 659 pw.println("packages"); 660 pw.increaseIndent(); 661 final ArrayMap<String, UsageStats> pkgStats = stats.packageStats; 662 final int pkgCount = pkgStats.size(); 663 for (int i = 0; i < pkgCount; i++) { 664 final UsageStats usageStats = pkgStats.valueAt(i); 665 pw.printPair("package", usageStats.mPackageName); 666 pw.printPair("totalTime", 667 formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates)); 668 pw.printPair("lastTime", formatDateTime(usageStats.mLastTimeUsed, prettyDates)); 669 pw.printPair("lastTimeSystem", 670 formatDateTime(usageStats.mLastTimeSystemUsed, prettyDates)); 671 pw.printPair("inactiveTime", 672 formatElapsedTime(screenOnTime - usageStats.mBeginIdleTime, prettyDates)); 673 pw.println(); 674 } 675 pw.decreaseIndent(); 676 677 pw.println("configurations"); 678 pw.increaseIndent(); 679 final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations; 680 final int configCount = configStats.size(); 681 for (int i = 0; i < configCount; i++) { 682 final ConfigurationStats config = configStats.valueAt(i); 683 pw.printPair("config", Configuration.resourceQualifierString(config.mConfiguration)); 684 pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates)); 685 pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates)); 686 pw.printPair("count", config.mActivationCount); 687 pw.println(); 688 } 689 pw.decreaseIndent(); 690 691 pw.println("events"); 692 pw.increaseIndent(); 693 final TimeSparseArray<UsageEvents.Event> events = stats.events; 694 final int eventCount = events != null ? events.size() : 0; 695 for (int i = 0; i < eventCount; i++) { 696 final UsageEvents.Event event = events.valueAt(i); 697 pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates)); 698 pw.printPair("type", eventToString(event.mEventType)); 699 pw.printPair("package", event.mPackage); 700 if (event.mClass != null) { 701 pw.printPair("class", event.mClass); 702 } 703 if (event.mConfiguration != null) { 704 pw.printPair("config", Configuration.resourceQualifierString(event.mConfiguration)); 705 } 706 pw.println(); 707 } 708 pw.decreaseIndent(); 709 pw.decreaseIndent(); 710 } 711 712 private static String intervalToString(int interval) { 713 switch (interval) { 714 case UsageStatsManager.INTERVAL_DAILY: 715 return "daily"; 716 case UsageStatsManager.INTERVAL_WEEKLY: 717 return "weekly"; 718 case UsageStatsManager.INTERVAL_MONTHLY: 719 return "monthly"; 720 case UsageStatsManager.INTERVAL_YEARLY: 721 return "yearly"; 722 default: 723 return "?"; 724 } 725 } 726 727 private static String eventToString(int eventType) { 728 switch (eventType) { 729 case UsageEvents.Event.NONE: 730 return "NONE"; 731 case UsageEvents.Event.MOVE_TO_BACKGROUND: 732 return "MOVE_TO_BACKGROUND"; 733 case UsageEvents.Event.MOVE_TO_FOREGROUND: 734 return "MOVE_TO_FOREGROUND"; 735 case UsageEvents.Event.END_OF_DAY: 736 return "END_OF_DAY"; 737 case UsageEvents.Event.CONTINUE_PREVIOUS_DAY: 738 return "CONTINUE_PREVIOUS_DAY"; 739 case UsageEvents.Event.CONFIGURATION_CHANGE: 740 return "CONFIGURATION_CHANGE"; 741 case UsageEvents.Event.SYSTEM_INTERACTION: 742 return "SYSTEM_INTERACTION"; 743 case UsageEvents.Event.USER_INTERACTION: 744 return "USER_INTERACTION"; 745 default: 746 return "UNKNOWN"; 747 } 748 } 749} 750