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