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