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