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