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