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