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 17 package com.android.server.usage; 18 19 import android.app.usage.ConfigurationStats; 20 import android.app.usage.EventList; 21 import android.app.usage.EventStats; 22 import android.app.usage.UsageEvents; 23 import android.app.usage.UsageStats; 24 import android.app.usage.UsageStatsManager; 25 import android.content.res.Configuration; 26 import android.os.SystemClock; 27 import android.content.Context; 28 import android.text.format.DateUtils; 29 import android.util.ArrayMap; 30 import android.util.ArraySet; 31 import android.util.Slog; 32 33 import com.android.internal.util.IndentingPrintWriter; 34 import com.android.server.usage.UsageStatsDatabase.StatCombiner; 35 36 import java.io.File; 37 import java.io.IOException; 38 import java.text.SimpleDateFormat; 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.List; 42 43 /** 44 * A per-user UsageStatsService. All methods are meant to be called with the main lock held 45 * in UsageStatsService. 46 */ 47 class UserUsageStatsService { 48 private static final String TAG = "UsageStatsService"; 49 private static final boolean DEBUG = UsageStatsService.DEBUG; 50 private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 51 private static final int sDateFormatFlags = 52 DateUtils.FORMAT_SHOW_DATE 53 | DateUtils.FORMAT_SHOW_TIME 54 | DateUtils.FORMAT_SHOW_YEAR 55 | DateUtils.FORMAT_NUMERIC_DATE; 56 57 private final Context mContext; 58 private final UsageStatsDatabase mDatabase; 59 private final IntervalStats[] mCurrentStats; 60 private boolean mStatsChanged = false; 61 private final UnixCalendar mDailyExpiryDate; 62 private final StatsUpdatedListener mListener; 63 private final String mLogPrefix; 64 private String mLastBackgroundedPackage; 65 private final int mUserId; 66 67 private static final long[] INTERVAL_LENGTH = new long[] { 68 UnixCalendar.DAY_IN_MILLIS, UnixCalendar.WEEK_IN_MILLIS, 69 UnixCalendar.MONTH_IN_MILLIS, UnixCalendar.YEAR_IN_MILLIS 70 }; 71 72 interface StatsUpdatedListener { 73 void onStatsUpdated(); 74 void onStatsReloaded(); 75 /** 76 * Callback that a system update was detected 77 * @param mUserId user that needs to be initialized 78 */ 79 void onNewUpdate(int mUserId); 80 } 81 82 UserUsageStatsService(Context context, int userId, File usageStatsDir, 83 StatsUpdatedListener listener) { 84 mContext = context; 85 mDailyExpiryDate = new UnixCalendar(0); 86 mDatabase = new UsageStatsDatabase(usageStatsDir); 87 mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT]; 88 mListener = listener; 89 mLogPrefix = "User[" + Integer.toString(userId) + "] "; 90 mUserId = userId; 91 } 92 93 void init(final long currentTimeMillis) { 94 mDatabase.init(currentTimeMillis); 95 96 int nullCount = 0; 97 for (int i = 0; i < mCurrentStats.length; i++) { 98 mCurrentStats[i] = mDatabase.getLatestUsageStats(i); 99 if (mCurrentStats[i] == null) { 100 // Find out how many intervals we don't have data for. 101 // Ideally it should be all or none. 102 nullCount++; 103 } 104 } 105 106 if (nullCount > 0) { 107 if (nullCount != mCurrentStats.length) { 108 // This is weird, but we shouldn't fail if something like this 109 // happens. 110 Slog.w(TAG, mLogPrefix + "Some stats have no latest available"); 111 } else { 112 // This must be first boot. 113 } 114 115 // By calling loadActiveStats, we will 116 // generate new stats for each bucket. 117 loadActiveStats(currentTimeMillis); 118 } else { 119 // Set up the expiry date to be one day from the latest daily stat. 120 // This may actually be today and we will rollover on the first event 121 // that is reported. 122 updateRolloverDeadline(); 123 } 124 125 // Now close off any events that were open at the time this was saved. 126 for (IntervalStats stat : mCurrentStats) { 127 final int pkgCount = stat.packageStats.size(); 128 for (int i = 0; i < pkgCount; i++) { 129 UsageStats pkgStats = stat.packageStats.valueAt(i); 130 if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND || 131 pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) { 132 stat.update(pkgStats.mPackageName, stat.lastTimeSaved, 133 UsageEvents.Event.END_OF_DAY); 134 notifyStatsChanged(); 135 } 136 } 137 138 stat.updateConfigurationStats(null, stat.lastTimeSaved); 139 } 140 141 if (mDatabase.isNewUpdate()) { 142 notifyNewUpdate(); 143 } 144 } 145 146 void onTimeChanged(long oldTime, long newTime) { 147 persistActiveStats(); 148 mDatabase.onTimeChanged(newTime - oldTime); 149 loadActiveStats(newTime); 150 } 151 152 void reportEvent(UsageEvents.Event event) { 153 if (DEBUG) { 154 Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage 155 + "[" + event.mTimeStamp + "]: " 156 + eventToString(event.mEventType)); 157 } 158 159 if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) { 160 // Need to rollover 161 rolloverStats(event.mTimeStamp); 162 } 163 164 final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY]; 165 166 final Configuration newFullConfig = event.mConfiguration; 167 if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE && 168 currentDailyStats.activeConfiguration != null) { 169 // Make the event configuration a delta. 170 event.mConfiguration = Configuration.generateDelta( 171 currentDailyStats.activeConfiguration, newFullConfig); 172 } 173 174 // Add the event to the daily list. 175 if (currentDailyStats.events == null) { 176 currentDailyStats.events = new EventList(); 177 } 178 if (event.mEventType != UsageEvents.Event.SYSTEM_INTERACTION) { 179 currentDailyStats.events.insert(event); 180 } 181 182 boolean incrementAppLaunch = false; 183 if (event.mEventType == UsageEvents.Event.MOVE_TO_FOREGROUND) { 184 if (event.mPackage != null && !event.mPackage.equals(mLastBackgroundedPackage)) { 185 incrementAppLaunch = true; 186 } 187 } else if (event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND) { 188 if (event.mPackage != null) { 189 mLastBackgroundedPackage = event.mPackage; 190 } 191 } 192 193 for (IntervalStats stats : mCurrentStats) { 194 switch (event.mEventType) { 195 case UsageEvents.Event.CONFIGURATION_CHANGE: { 196 stats.updateConfigurationStats(newFullConfig, event.mTimeStamp); 197 } break; 198 case UsageEvents.Event.CHOOSER_ACTION: { 199 stats.updateChooserCounts(event.mPackage, event.mContentType, event.mAction); 200 String[] annotations = event.mContentAnnotations; 201 if (annotations != null) { 202 for (String annotation : annotations) { 203 stats.updateChooserCounts(event.mPackage, annotation, event.mAction); 204 } 205 } 206 } break; 207 case UsageEvents.Event.SCREEN_INTERACTIVE: { 208 stats.updateScreenInteractive(event.mTimeStamp); 209 } break; 210 case UsageEvents.Event.SCREEN_NON_INTERACTIVE: { 211 stats.updateScreenNonInteractive(event.mTimeStamp); 212 } break; 213 case UsageEvents.Event.KEYGUARD_SHOWN: { 214 stats.updateKeyguardShown(event.mTimeStamp); 215 } break; 216 case UsageEvents.Event.KEYGUARD_HIDDEN: { 217 stats.updateKeyguardHidden(event.mTimeStamp); 218 } break; 219 default: { 220 stats.update(event.mPackage, event.mTimeStamp, event.mEventType); 221 if (incrementAppLaunch) { 222 stats.incrementAppLaunchCount(event.mPackage); 223 } 224 } break; 225 } 226 } 227 228 notifyStatsChanged(); 229 } 230 231 private static final StatCombiner<UsageStats> sUsageStatsCombiner = 232 new StatCombiner<UsageStats>() { 233 @Override 234 public void combine(IntervalStats stats, boolean mutable, 235 List<UsageStats> accResult) { 236 if (!mutable) { 237 accResult.addAll(stats.packageStats.values()); 238 return; 239 } 240 241 final int statCount = stats.packageStats.size(); 242 for (int i = 0; i < statCount; i++) { 243 accResult.add(new UsageStats(stats.packageStats.valueAt(i))); 244 } 245 } 246 }; 247 248 private static final StatCombiner<ConfigurationStats> sConfigStatsCombiner = 249 new StatCombiner<ConfigurationStats>() { 250 @Override 251 public void combine(IntervalStats stats, boolean mutable, 252 List<ConfigurationStats> accResult) { 253 if (!mutable) { 254 accResult.addAll(stats.configurations.values()); 255 return; 256 } 257 258 final int configCount = stats.configurations.size(); 259 for (int i = 0; i < configCount; i++) { 260 accResult.add(new ConfigurationStats(stats.configurations.valueAt(i))); 261 } 262 } 263 }; 264 265 private static final StatCombiner<EventStats> sEventStatsCombiner = 266 new StatCombiner<EventStats>() { 267 @Override 268 public void combine(IntervalStats stats, boolean mutable, 269 List<EventStats> accResult) { 270 stats.addEventStatsTo(accResult); 271 } 272 }; 273 274 /** 275 * Generic query method that selects the appropriate IntervalStats for the specified time range 276 * and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner} 277 * provided to select the stats to use from the IntervalStats object. 278 */ 279 private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime, 280 StatCombiner<T> combiner) { 281 if (intervalType == UsageStatsManager.INTERVAL_BEST) { 282 intervalType = mDatabase.findBestFitBucket(beginTime, endTime); 283 if (intervalType < 0) { 284 // Nothing saved to disk yet, so every stat is just as equal (no rollover has 285 // occurred. 286 intervalType = UsageStatsManager.INTERVAL_DAILY; 287 } 288 } 289 290 if (intervalType < 0 || intervalType >= mCurrentStats.length) { 291 if (DEBUG) { 292 Slog.d(TAG, mLogPrefix + "Bad intervalType used " + intervalType); 293 } 294 return null; 295 } 296 297 final IntervalStats currentStats = mCurrentStats[intervalType]; 298 299 if (DEBUG) { 300 Slog.d(TAG, mLogPrefix + "SELECT * FROM " + intervalType + " WHERE beginTime >= " 301 + beginTime + " AND endTime < " + endTime); 302 } 303 304 if (beginTime >= currentStats.endTime) { 305 if (DEBUG) { 306 Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is " 307 + currentStats.endTime); 308 } 309 // Nothing newer available. 310 return null; 311 } 312 313 // Truncate the endTime to just before the in-memory stats. Then, we'll append the 314 // in-memory stats to the results (if necessary) so as to avoid writing to disk too 315 // often. 316 final long truncatedEndTime = Math.min(currentStats.beginTime, endTime); 317 318 // Get the stats from disk. 319 List<T> results = mDatabase.queryUsageStats(intervalType, beginTime, 320 truncatedEndTime, combiner); 321 if (DEBUG) { 322 Slog.d(TAG, "Got " + (results != null ? results.size() : 0) + " results from disk"); 323 Slog.d(TAG, "Current stats beginTime=" + currentStats.beginTime + 324 " endTime=" + currentStats.endTime); 325 } 326 327 // Now check if the in-memory stats match the range and add them if they do. 328 if (beginTime < currentStats.endTime && endTime > currentStats.beginTime) { 329 if (DEBUG) { 330 Slog.d(TAG, mLogPrefix + "Returning in-memory stats"); 331 } 332 333 if (results == null) { 334 results = new ArrayList<>(); 335 } 336 combiner.combine(currentStats, true, results); 337 } 338 339 if (DEBUG) { 340 Slog.d(TAG, mLogPrefix + "Results: " + (results != null ? results.size() : 0)); 341 } 342 return results; 343 } 344 345 List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) { 346 return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner); 347 } 348 349 List<ConfigurationStats> queryConfigurationStats(int bucketType, long beginTime, long endTime) { 350 return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner); 351 } 352 353 List<EventStats> queryEventStats(int bucketType, long beginTime, long endTime) { 354 return queryStats(bucketType, beginTime, endTime, sEventStatsCombiner); 355 } 356 357 UsageEvents queryEvents(final long beginTime, final long endTime, 358 boolean obfuscateInstantApps) { 359 final ArraySet<String> names = new ArraySet<>(); 360 List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY, 361 beginTime, endTime, new StatCombiner<UsageEvents.Event>() { 362 @Override 363 public void combine(IntervalStats stats, boolean mutable, 364 List<UsageEvents.Event> accumulatedResult) { 365 if (stats.events == null) { 366 return; 367 } 368 369 final int startIndex = stats.events.firstIndexOnOrAfter(beginTime); 370 final int size = stats.events.size(); 371 for (int i = startIndex; i < size; i++) { 372 if (stats.events.get(i).mTimeStamp >= endTime) { 373 return; 374 } 375 376 UsageEvents.Event event = stats.events.get(i); 377 if (obfuscateInstantApps) { 378 event = event.getObfuscatedIfInstantApp(); 379 } 380 names.add(event.mPackage); 381 if (event.mClass != null) { 382 names.add(event.mClass); 383 } 384 accumulatedResult.add(event); 385 } 386 } 387 }); 388 389 if (results == null || results.isEmpty()) { 390 return null; 391 } 392 393 String[] table = names.toArray(new String[names.size()]); 394 Arrays.sort(table); 395 return new UsageEvents(results, table); 396 } 397 398 UsageEvents queryEventsForPackage(final long beginTime, final long endTime, 399 final String packageName) { 400 final ArraySet<String> names = new ArraySet<>(); 401 names.add(packageName); 402 final List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY, 403 beginTime, endTime, (stats, mutable, accumulatedResult) -> { 404 if (stats.events == null) { 405 return; 406 } 407 408 final int startIndex = stats.events.firstIndexOnOrAfter(beginTime); 409 final int size = stats.events.size(); 410 for (int i = startIndex; i < size; i++) { 411 if (stats.events.get(i).mTimeStamp >= endTime) { 412 return; 413 } 414 415 final UsageEvents.Event event = stats.events.get(i); 416 if (!packageName.equals(event.mPackage)) { 417 continue; 418 } 419 if (event.mClass != null) { 420 names.add(event.mClass); 421 } 422 accumulatedResult.add(event); 423 } 424 }); 425 426 if (results == null || results.isEmpty()) { 427 return null; 428 } 429 430 final String[] table = names.toArray(new String[names.size()]); 431 Arrays.sort(table); 432 return new UsageEvents(results, table); 433 } 434 435 void persistActiveStats() { 436 if (mStatsChanged) { 437 Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk"); 438 try { 439 for (int i = 0; i < mCurrentStats.length; i++) { 440 mDatabase.putUsageStats(i, mCurrentStats[i]); 441 } 442 mStatsChanged = false; 443 } catch (IOException e) { 444 Slog.e(TAG, mLogPrefix + "Failed to persist active stats", e); 445 } 446 } 447 } 448 449 private void rolloverStats(final long currentTimeMillis) { 450 final long startTime = SystemClock.elapsedRealtime(); 451 Slog.i(TAG, mLogPrefix + "Rolling over usage stats"); 452 453 // Finish any ongoing events with an END_OF_DAY event. Make a note of which components 454 // need a new CONTINUE_PREVIOUS_DAY entry. 455 final Configuration previousConfig = 456 mCurrentStats[UsageStatsManager.INTERVAL_DAILY].activeConfiguration; 457 ArraySet<String> continuePreviousDay = new ArraySet<>(); 458 for (IntervalStats stat : mCurrentStats) { 459 final int pkgCount = stat.packageStats.size(); 460 for (int i = 0; i < pkgCount; i++) { 461 UsageStats pkgStats = stat.packageStats.valueAt(i); 462 if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND || 463 pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) { 464 continuePreviousDay.add(pkgStats.mPackageName); 465 stat.update(pkgStats.mPackageName, mDailyExpiryDate.getTimeInMillis() - 1, 466 UsageEvents.Event.END_OF_DAY); 467 notifyStatsChanged(); 468 } 469 } 470 471 stat.updateConfigurationStats(null, mDailyExpiryDate.getTimeInMillis() - 1); 472 stat.commitTime(mDailyExpiryDate.getTimeInMillis() - 1); 473 } 474 475 persistActiveStats(); 476 mDatabase.prune(currentTimeMillis); 477 loadActiveStats(currentTimeMillis); 478 479 final int continueCount = continuePreviousDay.size(); 480 for (int i = 0; i < continueCount; i++) { 481 String name = continuePreviousDay.valueAt(i); 482 final long beginTime = mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime; 483 for (IntervalStats stat : mCurrentStats) { 484 stat.update(name, beginTime, UsageEvents.Event.CONTINUE_PREVIOUS_DAY); 485 stat.updateConfigurationStats(previousConfig, beginTime); 486 notifyStatsChanged(); 487 } 488 } 489 persistActiveStats(); 490 491 final long totalTime = SystemClock.elapsedRealtime() - startTime; 492 Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime 493 + " milliseconds"); 494 } 495 496 private void notifyStatsChanged() { 497 if (!mStatsChanged) { 498 mStatsChanged = true; 499 mListener.onStatsUpdated(); 500 } 501 } 502 503 private void notifyNewUpdate() { 504 mListener.onNewUpdate(mUserId); 505 } 506 507 private void loadActiveStats(final long currentTimeMillis) { 508 for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) { 509 final IntervalStats stats = mDatabase.getLatestUsageStats(intervalType); 510 if (stats != null && currentTimeMillis - 500 >= stats.endTime && 511 currentTimeMillis < stats.beginTime + INTERVAL_LENGTH[intervalType]) { 512 if (DEBUG) { 513 Slog.d(TAG, mLogPrefix + "Loading existing stats @ " + 514 sDateFormat.format(stats.beginTime) + "(" + stats.beginTime + 515 ") for interval " + intervalType); 516 } 517 mCurrentStats[intervalType] = stats; 518 } else { 519 // No good fit remains. 520 if (DEBUG) { 521 Slog.d(TAG, "Creating new stats @ " + 522 sDateFormat.format(currentTimeMillis) + "(" + 523 currentTimeMillis + ") for interval " + intervalType); 524 } 525 526 mCurrentStats[intervalType] = new IntervalStats(); 527 mCurrentStats[intervalType].beginTime = currentTimeMillis; 528 mCurrentStats[intervalType].endTime = currentTimeMillis + 1; 529 } 530 } 531 532 mStatsChanged = false; 533 updateRolloverDeadline(); 534 535 // Tell the listener that the stats reloaded, which may have changed idle states. 536 mListener.onStatsReloaded(); 537 } 538 539 private void updateRolloverDeadline() { 540 mDailyExpiryDate.setTimeInMillis( 541 mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime); 542 mDailyExpiryDate.addDays(1); 543 Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " + 544 sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" + 545 mDailyExpiryDate.getTimeInMillis() + ")"); 546 } 547 548 // 549 // -- DUMP related methods -- 550 // 551 552 void checkin(final IndentingPrintWriter pw) { 553 mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() { 554 @Override 555 public boolean checkin(IntervalStats stats) { 556 printIntervalStats(pw, stats, false, false, null); 557 return true; 558 } 559 }); 560 } 561 562 void dump(IndentingPrintWriter pw, String pkg) { 563 dump(pw, pkg, false); 564 } 565 void dump(IndentingPrintWriter pw, String pkg, boolean compact) { 566 printLast24HrEvents(pw, !compact, pkg); 567 for (int interval = 0; interval < mCurrentStats.length; interval++) { 568 pw.print("In-memory "); 569 pw.print(intervalToString(interval)); 570 pw.println(" stats"); 571 printIntervalStats(pw, mCurrentStats[interval], !compact, true, pkg); 572 } 573 } 574 575 private String formatDateTime(long dateTime, boolean pretty) { 576 if (pretty) { 577 return "\"" + sDateFormat.format(dateTime)+ "\""; 578 } 579 return Long.toString(dateTime); 580 } 581 582 private String formatElapsedTime(long elapsedTime, boolean pretty) { 583 if (pretty) { 584 return "\"" + DateUtils.formatElapsedTime(elapsedTime / 1000) + "\""; 585 } 586 return Long.toString(elapsedTime); 587 } 588 589 590 void printEvent(IndentingPrintWriter pw, UsageEvents.Event event, boolean prettyDates) { 591 pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates)); 592 pw.printPair("type", eventToString(event.mEventType)); 593 pw.printPair("package", event.mPackage); 594 if (event.mClass != null) { 595 pw.printPair("class", event.mClass); 596 } 597 if (event.mConfiguration != null) { 598 pw.printPair("config", Configuration.resourceQualifierString(event.mConfiguration)); 599 } 600 if (event.mShortcutId != null) { 601 pw.printPair("shortcutId", event.mShortcutId); 602 } 603 if (event.mEventType == UsageEvents.Event.STANDBY_BUCKET_CHANGED) { 604 pw.printPair("standbyBucket", event.getStandbyBucket()); 605 pw.printPair("reason", UsageStatsManager.reasonToString(event.getStandbyReason())); 606 } 607 pw.printHexPair("flags", event.mFlags); 608 pw.println(); 609 } 610 611 void printLast24HrEvents(IndentingPrintWriter pw, boolean prettyDates, final String pkg) { 612 final long endTime = System.currentTimeMillis(); 613 UnixCalendar yesterday = new UnixCalendar(endTime); 614 yesterday.addDays(-1); 615 616 final long beginTime = yesterday.getTimeInMillis(); 617 618 List<UsageEvents.Event> events = queryStats(UsageStatsManager.INTERVAL_DAILY, 619 beginTime, endTime, new StatCombiner<UsageEvents.Event>() { 620 @Override 621 public void combine(IntervalStats stats, boolean mutable, 622 List<UsageEvents.Event> accumulatedResult) { 623 if (stats.events == null) { 624 return; 625 } 626 627 final int startIndex = stats.events.firstIndexOnOrAfter(beginTime); 628 final int size = stats.events.size(); 629 for (int i = startIndex; i < size; i++) { 630 if (stats.events.get(i).mTimeStamp >= endTime) { 631 return; 632 } 633 634 UsageEvents.Event event = stats.events.get(i); 635 if (pkg != null && !pkg.equals(event.mPackage)) { 636 continue; 637 } 638 accumulatedResult.add(event); 639 } 640 } 641 }); 642 643 pw.print("Last 24 hour events ("); 644 if (prettyDates) { 645 pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext, 646 beginTime, endTime, sDateFormatFlags) + "\""); 647 } else { 648 pw.printPair("beginTime", beginTime); 649 pw.printPair("endTime", endTime); 650 } 651 pw.println(")"); 652 if (events != null) { 653 pw.increaseIndent(); 654 for (UsageEvents.Event event : events) { 655 printEvent(pw, event, prettyDates); 656 } 657 pw.decreaseIndent(); 658 } 659 } 660 661 void printEventAggregation(IndentingPrintWriter pw, String label, 662 IntervalStats.EventTracker tracker, boolean prettyDates) { 663 if (tracker.count != 0 || tracker.duration != 0) { 664 pw.print(label); 665 pw.print(": "); 666 pw.print(tracker.count); 667 pw.print("x for "); 668 pw.print(formatElapsedTime(tracker.duration, prettyDates)); 669 if (tracker.curStartTime != 0) { 670 pw.print(" (now running, started at "); 671 formatDateTime(tracker.curStartTime, prettyDates); 672 pw.print(")"); 673 } 674 pw.println(); 675 } 676 } 677 678 void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, 679 boolean prettyDates, boolean skipEvents, String pkg) { 680 if (prettyDates) { 681 pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext, 682 stats.beginTime, stats.endTime, sDateFormatFlags) + "\""); 683 } else { 684 pw.printPair("beginTime", stats.beginTime); 685 pw.printPair("endTime", stats.endTime); 686 } 687 pw.println(); 688 pw.increaseIndent(); 689 pw.println("packages"); 690 pw.increaseIndent(); 691 final ArrayMap<String, UsageStats> pkgStats = stats.packageStats; 692 final int pkgCount = pkgStats.size(); 693 for (int i = 0; i < pkgCount; i++) { 694 final UsageStats usageStats = pkgStats.valueAt(i); 695 if (pkg != null && !pkg.equals(usageStats.mPackageName)) { 696 continue; 697 } 698 pw.printPair("package", usageStats.mPackageName); 699 pw.printPair("totalTime", 700 formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates)); 701 pw.printPair("lastTime", formatDateTime(usageStats.mLastTimeUsed, prettyDates)); 702 pw.printPair("appLaunchCount", usageStats.mAppLaunchCount); 703 pw.println(); 704 } 705 pw.decreaseIndent(); 706 707 pw.println(); 708 pw.println("ChooserCounts"); 709 pw.increaseIndent(); 710 for (UsageStats usageStats : pkgStats.values()) { 711 if (pkg != null && !pkg.equals(usageStats.mPackageName)) { 712 continue; 713 } 714 pw.printPair("package", usageStats.mPackageName); 715 if (usageStats.mChooserCounts != null) { 716 final int chooserCountSize = usageStats.mChooserCounts.size(); 717 for (int i = 0; i < chooserCountSize; i++) { 718 final String action = usageStats.mChooserCounts.keyAt(i); 719 final ArrayMap<String, Integer> counts = usageStats.mChooserCounts.valueAt(i); 720 final int annotationSize = counts.size(); 721 for (int j = 0; j < annotationSize; j++) { 722 final String key = counts.keyAt(j); 723 final int count = counts.valueAt(j); 724 if (count != 0) { 725 pw.printPair("ChooserCounts", action + ":" + key + " is " + 726 Integer.toString(count)); 727 pw.println(); 728 } 729 } 730 } 731 } 732 pw.println(); 733 } 734 pw.decreaseIndent(); 735 736 if (pkg == null) { 737 pw.println("configurations"); 738 pw.increaseIndent(); 739 final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations; 740 final int configCount = configStats.size(); 741 for (int i = 0; i < configCount; i++) { 742 final ConfigurationStats config = configStats.valueAt(i); 743 pw.printPair("config", Configuration.resourceQualifierString( 744 config.mConfiguration)); 745 pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates)); 746 pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates)); 747 pw.printPair("count", config.mActivationCount); 748 pw.println(); 749 } 750 pw.decreaseIndent(); 751 pw.println("event aggregations"); 752 pw.increaseIndent(); 753 printEventAggregation(pw, "screen-interactive", stats.interactiveTracker, 754 prettyDates); 755 printEventAggregation(pw, "screen-non-interactive", stats.nonInteractiveTracker, 756 prettyDates); 757 printEventAggregation(pw, "keyguard-shown", stats.keyguardShownTracker, 758 prettyDates); 759 printEventAggregation(pw, "keyguard-hidden", stats.keyguardHiddenTracker, 760 prettyDates); 761 pw.decreaseIndent(); 762 } 763 764 // The last 24 hours of events is already printed in the non checkin dump 765 // No need to repeat here. 766 if (!skipEvents) { 767 pw.println("events"); 768 pw.increaseIndent(); 769 final EventList events = stats.events; 770 final int eventCount = events != null ? events.size() : 0; 771 for (int i = 0; i < eventCount; i++) { 772 final UsageEvents.Event event = events.get(i); 773 if (pkg != null && !pkg.equals(event.mPackage)) { 774 continue; 775 } 776 printEvent(pw, event, prettyDates); 777 } 778 pw.decreaseIndent(); 779 } 780 pw.decreaseIndent(); 781 } 782 783 private static String intervalToString(int interval) { 784 switch (interval) { 785 case UsageStatsManager.INTERVAL_DAILY: 786 return "daily"; 787 case UsageStatsManager.INTERVAL_WEEKLY: 788 return "weekly"; 789 case UsageStatsManager.INTERVAL_MONTHLY: 790 return "monthly"; 791 case UsageStatsManager.INTERVAL_YEARLY: 792 return "yearly"; 793 default: 794 return "?"; 795 } 796 } 797 798 private static String eventToString(int eventType) { 799 switch (eventType) { 800 case UsageEvents.Event.NONE: 801 return "NONE"; 802 case UsageEvents.Event.MOVE_TO_BACKGROUND: 803 return "MOVE_TO_BACKGROUND"; 804 case UsageEvents.Event.MOVE_TO_FOREGROUND: 805 return "MOVE_TO_FOREGROUND"; 806 case UsageEvents.Event.END_OF_DAY: 807 return "END_OF_DAY"; 808 case UsageEvents.Event.CONTINUE_PREVIOUS_DAY: 809 return "CONTINUE_PREVIOUS_DAY"; 810 case UsageEvents.Event.CONFIGURATION_CHANGE: 811 return "CONFIGURATION_CHANGE"; 812 case UsageEvents.Event.SYSTEM_INTERACTION: 813 return "SYSTEM_INTERACTION"; 814 case UsageEvents.Event.USER_INTERACTION: 815 return "USER_INTERACTION"; 816 case UsageEvents.Event.SHORTCUT_INVOCATION: 817 return "SHORTCUT_INVOCATION"; 818 case UsageEvents.Event.CHOOSER_ACTION: 819 return "CHOOSER_ACTION"; 820 case UsageEvents.Event.NOTIFICATION_SEEN: 821 return "NOTIFICATION_SEEN"; 822 case UsageEvents.Event.STANDBY_BUCKET_CHANGED: 823 return "STANDBY_BUCKET_CHANGED"; 824 case UsageEvents.Event.NOTIFICATION_INTERRUPTION: 825 return "NOTIFICATION_INTERRUPTION"; 826 case UsageEvents.Event.SLICE_PINNED: 827 return "SLICE_PINNED"; 828 case UsageEvents.Event.SLICE_PINNED_PRIV: 829 return "SLICE_PINNED_PRIV"; 830 case UsageEvents.Event.SCREEN_INTERACTIVE: 831 return "SCREEN_INTERACTIVE"; 832 case UsageEvents.Event.SCREEN_NON_INTERACTIVE: 833 return "SCREEN_NON_INTERACTIVE"; 834 default: 835 return "UNKNOWN"; 836 } 837 } 838 839 byte[] getBackupPayload(String key){ 840 return mDatabase.getBackupPayload(key); 841 } 842 843 void applyRestoredPayload(String key, byte[] payload){ 844 mDatabase.applyRestoredPayload(key, payload); 845 } 846 } 847