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.UsageEvents.HIDE_LOCUS_EVENTS; 22 import static android.app.usage.UsageEvents.HIDE_SHORTCUT_EVENTS; 23 import static android.app.usage.UsageEvents.OBFUSCATE_INSTANT_APPS; 24 import static android.app.usage.UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS; 25 import static android.app.usage.UsageStatsManager.INTERVAL_BEST; 26 import static android.app.usage.UsageStatsManager.INTERVAL_COUNT; 27 import static android.app.usage.UsageStatsManager.INTERVAL_DAILY; 28 import static android.app.usage.UsageStatsManager.INTERVAL_MONTHLY; 29 import static android.app.usage.UsageStatsManager.INTERVAL_WEEKLY; 30 import static android.app.usage.UsageStatsManager.INTERVAL_YEARLY; 31 32 import android.annotation.NonNull; 33 import android.annotation.Nullable; 34 import android.app.usage.ConfigurationStats; 35 import android.app.usage.EventList; 36 import android.app.usage.EventStats; 37 import android.app.usage.UsageEvents; 38 import android.app.usage.UsageEvents.Event; 39 import android.app.usage.UsageStats; 40 import android.app.usage.UsageStatsManager; 41 import android.content.Context; 42 import android.content.res.Configuration; 43 import android.os.SystemClock; 44 import android.os.UserHandle; 45 import android.text.format.DateUtils; 46 import android.util.ArrayMap; 47 import android.util.ArraySet; 48 import android.util.AtomicFile; 49 import android.util.LongSparseArray; 50 import android.util.Slog; 51 import android.util.SparseArrayMap; 52 import android.util.SparseIntArray; 53 54 import com.android.internal.util.ArrayUtils; 55 import com.android.internal.util.CollectionUtils; 56 import com.android.internal.util.IndentingPrintWriter; 57 import com.android.server.usage.UsageStatsDatabase.StatCombiner; 58 59 import java.io.File; 60 import java.io.IOException; 61 import java.text.SimpleDateFormat; 62 import java.util.ArrayList; 63 import java.util.Arrays; 64 import java.util.HashMap; 65 import java.util.List; 66 import java.util.Set; 67 68 /** 69 * A per-user UsageStatsService. All methods are meant to be called with the main lock held 70 * in UsageStatsService. 71 */ 72 class UserUsageStatsService { 73 private static final String TAG = UsageStatsService.TAG; 74 private static final boolean DEBUG = UsageStatsService.DEBUG; 75 private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 76 private static final int sDateFormatFlags = 77 DateUtils.FORMAT_SHOW_DATE 78 | DateUtils.FORMAT_SHOW_TIME 79 | DateUtils.FORMAT_SHOW_YEAR 80 | DateUtils.FORMAT_NUMERIC_DATE; 81 82 private final Context mContext; 83 private final UsageStatsDatabase mDatabase; 84 private final IntervalStats[] mCurrentStats; 85 private boolean mStatsChanged = false; 86 private final UnixCalendar mDailyExpiryDate; 87 private final StatsUpdatedListener mListener; 88 private final String mLogPrefix; 89 private String mLastBackgroundedPackage; 90 private final int mUserId; 91 private long mRealTimeSnapshot; 92 private long mSystemTimeSnapshot; 93 94 private static final long[] INTERVAL_LENGTH = new long[] { 95 UnixCalendar.DAY_IN_MILLIS, UnixCalendar.WEEK_IN_MILLIS, 96 UnixCalendar.MONTH_IN_MILLIS, UnixCalendar.YEAR_IN_MILLIS 97 }; 98 99 interface StatsUpdatedListener { onStatsUpdated()100 void onStatsUpdated(); onStatsReloaded()101 void onStatsReloaded(); 102 /** 103 * Callback that a system update was detected 104 * @param mUserId user that needs to be initialized 105 */ onNewUpdate(int mUserId)106 void onNewUpdate(int mUserId); 107 } 108 109 private static final class CachedEarlyEvents { 110 public long searchBeginTime; 111 112 public long eventTime; 113 114 @Nullable 115 public List<UsageEvents.Event> events; 116 } 117 118 /** 119 * Mapping of {@link UsageEvents.Event} event value to packageName-cached early usage event. 120 * This is used to reduce how much we need to interact with the underlying database to get the 121 * earliest event for a specific package. 122 */ 123 private final SparseArrayMap<String, CachedEarlyEvents> mCachedEarlyEvents = 124 new SparseArrayMap<>(); 125 UserUsageStatsService(Context context, int userId, File usageStatsDir, StatsUpdatedListener listener)126 UserUsageStatsService(Context context, int userId, File usageStatsDir, 127 StatsUpdatedListener listener) { 128 mContext = context; 129 mDailyExpiryDate = new UnixCalendar(0); 130 mDatabase = new UsageStatsDatabase(usageStatsDir); 131 mCurrentStats = new IntervalStats[INTERVAL_COUNT]; 132 mListener = listener; 133 mLogPrefix = "User[" + Integer.toString(userId) + "] "; 134 mUserId = userId; 135 mRealTimeSnapshot = SystemClock.elapsedRealtime(); 136 mSystemTimeSnapshot = System.currentTimeMillis(); 137 } 138 init(final long currentTimeMillis, HashMap<String, Long> installedPackages, boolean deleteObsoleteData)139 void init(final long currentTimeMillis, HashMap<String, Long> installedPackages, 140 boolean deleteObsoleteData) { 141 readPackageMappingsLocked(installedPackages, deleteObsoleteData); 142 mDatabase.init(currentTimeMillis); 143 if (mDatabase.wasUpgradePerformed()) { 144 mDatabase.prunePackagesDataOnUpgrade(installedPackages); 145 } 146 147 int nullCount = 0; 148 for (int i = 0; i < mCurrentStats.length; i++) { 149 mCurrentStats[i] = mDatabase.getLatestUsageStats(i); 150 if (mCurrentStats[i] == null) { 151 // Find out how many intervals we don't have data for. 152 // Ideally it should be all or none. 153 nullCount++; 154 } 155 } 156 157 if (nullCount > 0) { 158 if (nullCount != mCurrentStats.length) { 159 // This is weird, but we shouldn't fail if something like this 160 // happens. 161 Slog.w(TAG, mLogPrefix + "Some stats have no latest available"); 162 } else { 163 // This must be first boot. 164 } 165 166 // By calling loadActiveStats, we will 167 // generate new stats for each bucket. 168 loadActiveStats(currentTimeMillis); 169 } else { 170 // Set up the expiry date to be one day from the latest daily stat. 171 // This may actually be today and we will rollover on the first event 172 // that is reported. 173 updateRolloverDeadline(); 174 } 175 176 // During system reboot, add a DEVICE_SHUTDOWN event to the end of event list, the timestamp 177 // is last time UsageStatsDatabase is persisted to disk or the last event's time whichever 178 // is higher (because the file system timestamp is round down to integral seconds). 179 // Also add a DEVICE_STARTUP event with current system timestamp. 180 final IntervalStats currentDailyStats = mCurrentStats[INTERVAL_DAILY]; 181 if (currentDailyStats != null) { 182 final Event shutdownEvent = new Event(DEVICE_SHUTDOWN, 183 Math.max(currentDailyStats.lastTimeSaved, currentDailyStats.endTime)); 184 shutdownEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME; 185 currentDailyStats.addEvent(shutdownEvent); 186 final Event startupEvent = new Event(DEVICE_STARTUP, System.currentTimeMillis()); 187 startupEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME; 188 currentDailyStats.addEvent(startupEvent); 189 } 190 191 if (mDatabase.isNewUpdate()) { 192 notifyNewUpdate(); 193 } 194 } 195 userStopped()196 void userStopped() { 197 // Flush events to disk immediately to guarantee persistence. 198 persistActiveStats(); 199 mCachedEarlyEvents.clear(); 200 } 201 onPackageRemoved(String packageName, long timeRemoved)202 int onPackageRemoved(String packageName, long timeRemoved) { 203 for (int i = mCachedEarlyEvents.numMaps() - 1; i >= 0; --i) { 204 final int eventType = mCachedEarlyEvents.keyAt(i); 205 mCachedEarlyEvents.delete(eventType, packageName); 206 } 207 return mDatabase.onPackageRemoved(packageName, timeRemoved); 208 } 209 readPackageMappingsLocked(HashMap<String, Long> installedPackages, boolean deleteObsoleteData)210 private void readPackageMappingsLocked(HashMap<String, Long> installedPackages, 211 boolean deleteObsoleteData) { 212 mDatabase.readMappingsLocked(); 213 // Package mappings for the system user are updated after 24 hours via a job scheduled by 214 // UsageStatsIdleService to ensure restored data is not lost on first boot. Additionally, 215 // this makes user service initialization a little quicker on subsequent boots. 216 if (mUserId != UserHandle.USER_SYSTEM && deleteObsoleteData) { 217 updatePackageMappingsLocked(installedPackages); 218 } 219 } 220 221 /** 222 * Compares the package mappings on disk with the ones currently installed and removes the 223 * mappings for those packages that have been uninstalled. 224 * This will only happen once per device boot, when the user is unlocked for the first time. 225 * If the user is the system user (user 0), this is delayed to ensure data for packages 226 * that were restored isn't removed before the restore is complete. 227 * 228 * @param installedPackages map of installed packages (package_name:package_install_time) 229 * @return {@code true} on a successful mappings update, {@code false} otherwise. 230 */ updatePackageMappingsLocked(HashMap<String, Long> installedPackages)231 boolean updatePackageMappingsLocked(HashMap<String, Long> installedPackages) { 232 if (ArrayUtils.isEmpty(installedPackages)) { 233 return true; 234 } 235 236 final long timeNow = System.currentTimeMillis(); 237 final ArrayList<String> removedPackages = new ArrayList<>(); 238 // populate list of packages that are found in the mappings but not in the installed list 239 for (int i = mDatabase.mPackagesTokenData.packagesToTokensMap.size() - 1; i >= 0; i--) { 240 final String packageName = mDatabase.mPackagesTokenData.packagesToTokensMap.keyAt(i); 241 if (!installedPackages.containsKey(packageName)) { 242 removedPackages.add(packageName); 243 } 244 } 245 if (removedPackages.isEmpty()) { 246 return true; 247 } 248 249 // remove packages in the mappings that are no longer installed and persist to disk 250 for (int i = removedPackages.size() - 1; i >= 0; i--) { 251 mDatabase.mPackagesTokenData.removePackage(removedPackages.get(i), timeNow); 252 } 253 try { 254 mDatabase.writeMappingsLocked(); 255 } catch (Exception e) { 256 Slog.w(TAG, "Unable to write updated package mappings file on service initialization."); 257 return false; 258 } 259 return true; 260 } 261 pruneUninstalledPackagesData()262 boolean pruneUninstalledPackagesData() { 263 return mDatabase.pruneUninstalledPackagesData(); 264 } 265 onTimeChanged(long oldTime, long newTime)266 private void onTimeChanged(long oldTime, long newTime) { 267 mCachedEarlyEvents.clear(); 268 persistActiveStats(); 269 mDatabase.onTimeChanged(newTime - oldTime); 270 loadActiveStats(newTime); 271 } 272 273 /** 274 * This should be the only way to get the time from the system. 275 */ checkAndGetTimeLocked()276 private long checkAndGetTimeLocked() { 277 final long actualSystemTime = System.currentTimeMillis(); 278 if (!UsageStatsService.ENABLE_TIME_CHANGE_CORRECTION) { 279 return actualSystemTime; 280 } 281 final long actualRealtime = SystemClock.elapsedRealtime(); 282 final long expectedSystemTime = (actualRealtime - mRealTimeSnapshot) + mSystemTimeSnapshot; 283 final long diffSystemTime = actualSystemTime - expectedSystemTime; 284 if (Math.abs(diffSystemTime) > UsageStatsService.TIME_CHANGE_THRESHOLD_MILLIS) { 285 // The time has changed. 286 Slog.i(TAG, mLogPrefix + "Time changed in by " + (diffSystemTime / 1000) + " seconds"); 287 onTimeChanged(expectedSystemTime, actualSystemTime); 288 mRealTimeSnapshot = actualRealtime; 289 mSystemTimeSnapshot = actualSystemTime; 290 } 291 return actualSystemTime; 292 } 293 294 /** 295 * Assuming the event's timestamp is measured in milliseconds since boot, 296 * convert it to a system wall time. 297 */ convertToSystemTimeLocked(Event event)298 private void convertToSystemTimeLocked(Event event) { 299 event.mTimeStamp = Math.max(0, event.mTimeStamp - mRealTimeSnapshot) + mSystemTimeSnapshot; 300 } 301 reportEvent(Event event)302 void reportEvent(Event event) { 303 if (DEBUG) { 304 Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage 305 + "[" + event.mTimeStamp + "]: " 306 + eventToString(event.mEventType)); 307 } 308 309 if (event.mEventType != Event.USER_INTERACTION 310 && event.mEventType != Event.APP_COMPONENT_USED) { 311 checkAndGetTimeLocked(); 312 convertToSystemTimeLocked(event); 313 } 314 315 if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) { 316 // Need to rollover 317 rolloverStats(event.mTimeStamp); 318 } 319 320 final IntervalStats currentDailyStats = mCurrentStats[INTERVAL_DAILY]; 321 322 final Configuration newFullConfig = event.mConfiguration; 323 if (event.mEventType == Event.CONFIGURATION_CHANGE 324 && currentDailyStats.activeConfiguration != null) { 325 // Make the event configuration a delta. 326 event.mConfiguration = Configuration.generateDelta( 327 currentDailyStats.activeConfiguration, newFullConfig); 328 } 329 330 if (event.mEventType != Event.SYSTEM_INTERACTION 331 // ACTIVITY_DESTROYED is a private event. If there is preceding ACTIVITY_STOPPED 332 // ACTIVITY_DESTROYED will be dropped. Otherwise it will be converted to 333 // ACTIVITY_STOPPED. 334 && event.mEventType != Event.ACTIVITY_DESTROYED 335 // FLUSH_TO_DISK is a private event. 336 && event.mEventType != Event.FLUSH_TO_DISK 337 // DEVICE_SHUTDOWN is added to event list after reboot. 338 && event.mEventType != Event.DEVICE_SHUTDOWN 339 // We aren't interested in every instance of the APP_COMPONENT_USED event. 340 && event.mEventType != Event.APP_COMPONENT_USED) { 341 currentDailyStats.addEvent(event); 342 } 343 344 boolean incrementAppLaunch = false; 345 if (event.mEventType == Event.ACTIVITY_RESUMED) { 346 if (event.mPackage != null && !event.mPackage.equals(mLastBackgroundedPackage)) { 347 incrementAppLaunch = true; 348 } 349 } else if (event.mEventType == Event.ACTIVITY_PAUSED) { 350 if (event.mPackage != null) { 351 mLastBackgroundedPackage = event.mPackage; 352 } 353 } 354 355 for (IntervalStats stats : mCurrentStats) { 356 switch (event.mEventType) { 357 case Event.CONFIGURATION_CHANGE: { 358 stats.updateConfigurationStats(newFullConfig, event.mTimeStamp); 359 } break; 360 case Event.CHOOSER_ACTION: { 361 stats.updateChooserCounts(event.mPackage, event.mContentType, event.mAction); 362 String[] annotations = event.mContentAnnotations; 363 if (annotations != null) { 364 for (String annotation : annotations) { 365 stats.updateChooserCounts(event.mPackage, annotation, event.mAction); 366 } 367 } 368 } break; 369 case Event.SCREEN_INTERACTIVE: { 370 stats.updateScreenInteractive(event.mTimeStamp); 371 } break; 372 case Event.SCREEN_NON_INTERACTIVE: { 373 stats.updateScreenNonInteractive(event.mTimeStamp); 374 } break; 375 case Event.KEYGUARD_SHOWN: { 376 stats.updateKeyguardShown(event.mTimeStamp); 377 } break; 378 case Event.KEYGUARD_HIDDEN: { 379 stats.updateKeyguardHidden(event.mTimeStamp); 380 } break; 381 default: { 382 stats.update(event.mPackage, event.getClassName(), 383 event.mTimeStamp, event.mEventType, event.mInstanceId); 384 if (incrementAppLaunch) { 385 stats.incrementAppLaunchCount(event.mPackage); 386 } 387 } break; 388 } 389 } 390 391 notifyStatsChanged(); 392 } 393 394 private static final StatCombiner<UsageStats> sUsageStatsCombiner = 395 new StatCombiner<UsageStats>() { 396 @Override 397 public boolean combine(IntervalStats stats, boolean mutable, 398 List<UsageStats> accResult) { 399 if (!mutable) { 400 accResult.addAll(stats.packageStats.values()); 401 return true; 402 } 403 404 final int statCount = stats.packageStats.size(); 405 for (int i = 0; i < statCount; i++) { 406 accResult.add(new UsageStats(stats.packageStats.valueAt(i))); 407 } 408 return true; 409 } 410 }; 411 412 private static final StatCombiner<ConfigurationStats> sConfigStatsCombiner = 413 new StatCombiner<ConfigurationStats>() { 414 @Override 415 public boolean combine(IntervalStats stats, boolean mutable, 416 List<ConfigurationStats> accResult) { 417 if (!mutable) { 418 accResult.addAll(stats.configurations.values()); 419 return true; 420 } 421 422 final int configCount = stats.configurations.size(); 423 for (int i = 0; i < configCount; i++) { 424 accResult.add(new ConfigurationStats(stats.configurations.valueAt(i))); 425 } 426 return true; 427 } 428 }; 429 430 private static final StatCombiner<EventStats> sEventStatsCombiner = 431 new StatCombiner<EventStats>() { 432 @Override 433 public boolean combine(IntervalStats stats, boolean mutable, 434 List<EventStats> accResult) { 435 stats.addEventStatsTo(accResult); 436 return true; 437 } 438 }; 439 validRange(long currentTime, long beginTime, long endTime)440 private static boolean validRange(long currentTime, long beginTime, long endTime) { 441 return beginTime <= currentTime && beginTime < endTime; 442 } 443 444 /** 445 * Generic query method that selects the appropriate IntervalStats for the specified time range 446 * and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner} 447 * provided to select the stats to use from the IntervalStats object. 448 */ 449 @Nullable queryStats(int intervalType, final long beginTime, final long endTime, StatCombiner<T> combiner, boolean skipEvents)450 private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime, 451 StatCombiner<T> combiner, boolean skipEvents) { 452 if (intervalType == INTERVAL_BEST) { 453 intervalType = mDatabase.findBestFitBucket(beginTime, endTime); 454 if (intervalType < 0) { 455 // Nothing saved to disk yet, so every stat is just as equal (no rollover has 456 // occurred. 457 intervalType = INTERVAL_DAILY; 458 } 459 } 460 461 if (intervalType < 0 || intervalType >= mCurrentStats.length) { 462 if (DEBUG) { 463 Slog.d(TAG, mLogPrefix + "Bad intervalType used " + intervalType); 464 } 465 return null; 466 } 467 468 final IntervalStats currentStats = mCurrentStats[intervalType]; 469 470 if (DEBUG) { 471 Slog.d(TAG, mLogPrefix + "SELECT * FROM " + intervalType + " WHERE beginTime >= " 472 + beginTime + " AND endTime < " + endTime); 473 } 474 475 if (beginTime >= currentStats.endTime) { 476 if (DEBUG) { 477 Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is " 478 + currentStats.endTime); 479 } 480 // Nothing newer available. 481 return null; 482 } 483 484 // Truncate the endTime to just before the in-memory stats. Then, we'll append the 485 // in-memory stats to the results (if necessary) so as to avoid writing to disk too 486 // often. 487 final long truncatedEndTime = Math.min(currentStats.beginTime, endTime); 488 489 // Get the stats from disk. 490 List<T> results = mDatabase.queryUsageStats(intervalType, beginTime, 491 truncatedEndTime, combiner, skipEvents); 492 if (DEBUG) { 493 Slog.d(TAG, "Got " + (results != null ? results.size() : 0) + " results from disk"); 494 Slog.d(TAG, "Current stats beginTime=" + currentStats.beginTime + 495 " endTime=" + currentStats.endTime); 496 } 497 498 // Now check if the in-memory stats match the range and add them if they do. 499 if (beginTime < currentStats.endTime && endTime > currentStats.beginTime) { 500 if (DEBUG) { 501 Slog.d(TAG, mLogPrefix + "Returning in-memory stats"); 502 } 503 504 if (results == null) { 505 results = new ArrayList<>(); 506 } 507 mDatabase.filterStats(currentStats); 508 combiner.combine(currentStats, true, results); 509 } 510 511 if (DEBUG) { 512 Slog.d(TAG, mLogPrefix + "Results: " + (results != null ? results.size() : 0)); 513 } 514 return results; 515 } 516 queryUsageStats(int bucketType, long beginTime, long endTime)517 List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) { 518 if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) { 519 return null; 520 } 521 return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner, true); 522 } 523 queryConfigurationStats(int bucketType, long beginTime, long endTime)524 List<ConfigurationStats> queryConfigurationStats(int bucketType, long beginTime, long endTime) { 525 if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) { 526 return null; 527 } 528 return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner, true); 529 } 530 queryEventStats(int bucketType, long beginTime, long endTime)531 List<EventStats> queryEventStats(int bucketType, long beginTime, long endTime) { 532 if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) { 533 return null; 534 } 535 return queryStats(bucketType, beginTime, endTime, sEventStatsCombiner, true); 536 } 537 queryEvents(final long beginTime, final long endTime, int flags, int[] eventTypeFilter, ArraySet<String> pkgNameFilter)538 UsageEvents queryEvents(final long beginTime, final long endTime, int flags, 539 int[] eventTypeFilter, ArraySet<String> pkgNameFilter) { 540 if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) { 541 return null; 542 } 543 544 // Ensure valid event type filter. 545 final boolean isQueryForAllEvents = ArrayUtils.isEmpty(eventTypeFilter); 546 final boolean isQueryForAllPackages = pkgNameFilter == null || pkgNameFilter.isEmpty(); 547 final boolean[] queryEventFilter = new boolean[Event.MAX_EVENT_TYPE + 1]; 548 if (!isQueryForAllEvents) { 549 for (int eventType : eventTypeFilter) { 550 if (eventType < Event.NONE || eventType > Event.MAX_EVENT_TYPE) { 551 throw new IllegalArgumentException("invalid event type: " + eventType); 552 } 553 queryEventFilter[eventType] = true; 554 } 555 } 556 final ArraySet<String> names = new ArraySet<>(); 557 List<Event> results = queryStats(INTERVAL_DAILY, 558 beginTime, endTime, new StatCombiner<Event>() { 559 @Override 560 public boolean combine(IntervalStats stats, boolean mutable, 561 List<Event> accumulatedResult) { 562 final int startIndex = stats.events.firstIndexOnOrAfter(beginTime); 563 final int size = stats.events.size(); 564 565 for (int i = startIndex; i < size; i++) { 566 Event event = stats.events.get(i); 567 if (event.mTimeStamp >= endTime) { 568 return false; 569 } 570 571 final int eventType = event.mEventType; 572 if (!isQueryForAllEvents && !queryEventFilter[eventType]) { 573 continue; 574 } 575 576 if (eventType == Event.SHORTCUT_INVOCATION 577 && (flags & HIDE_SHORTCUT_EVENTS) == HIDE_SHORTCUT_EVENTS) { 578 continue; 579 } 580 if (eventType == Event.LOCUS_ID_SET 581 && (flags & HIDE_LOCUS_EVENTS) == HIDE_LOCUS_EVENTS) { 582 continue; 583 } 584 if ((eventType == Event.NOTIFICATION_SEEN 585 || eventType == Event.NOTIFICATION_INTERRUPTION) 586 && (flags & OBFUSCATE_NOTIFICATION_EVENTS) 587 == OBFUSCATE_NOTIFICATION_EVENTS) { 588 event = event.getObfuscatedNotificationEvent(); 589 } 590 if ((flags & OBFUSCATE_INSTANT_APPS) == OBFUSCATE_INSTANT_APPS) { 591 event = event.getObfuscatedIfInstantApp(); 592 } 593 594 if (!isQueryForAllPackages && !pkgNameFilter.contains(event.mPackage)) { 595 continue; 596 } 597 598 if (event.mPackage != null) { 599 names.add(event.mPackage); 600 } 601 if (event.mClass != null) { 602 names.add(event.mClass); 603 } 604 if (event.mTaskRootPackage != null) { 605 names.add(event.mTaskRootPackage); 606 } 607 if (event.mTaskRootClass != null) { 608 names.add(event.mTaskRootClass); 609 } 610 accumulatedResult.add(event); 611 } 612 return true; 613 } 614 }, false); 615 616 if (results == null || results.isEmpty()) { 617 return null; 618 } 619 620 String[] table = names.toArray(new String[names.size()]); 621 Arrays.sort(table); 622 return new UsageEvents(results, table, true); 623 } 624 625 /** 626 * Returns a {@link UsageEvents} object whose events list contains only the earliest event seen 627 * for each app as well as the earliest event of {@code eventType} seen for each app. 628 */ 629 @Nullable queryEarliestAppEvents(final long beginTime, final long endTime, final int eventType)630 UsageEvents queryEarliestAppEvents(final long beginTime, final long endTime, 631 final int eventType) { 632 if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) { 633 return null; 634 } 635 final ArraySet<String> names = new ArraySet<>(); 636 final ArraySet<String> eventSuccess = new ArraySet<>(); 637 final List<Event> results = queryStats(INTERVAL_DAILY, 638 beginTime, endTime, (stats, mutable, accumulatedResult) -> { 639 final int startIndex = stats.events.firstIndexOnOrAfter(beginTime); 640 final int size = stats.events.size(); 641 for (int i = startIndex; i < size; i++) { 642 final Event event = stats.events.get(i); 643 if (event.getTimeStamp() >= endTime) { 644 return false; 645 } 646 if (event.getPackageName() == null) { 647 continue; 648 } 649 if (eventSuccess.contains(event.getPackageName())) { 650 continue; 651 } 652 653 final boolean firstEvent = names.add(event.getPackageName()); 654 655 if (event.getEventType() == eventType) { 656 accumulatedResult.add(event); 657 eventSuccess.add(event.getPackageName()); 658 } else if (firstEvent) { 659 // Save the earliest found event for the app, even if it doesn't match. 660 accumulatedResult.add(event); 661 } 662 } 663 return true; 664 }, false); 665 666 if (results == null || results.isEmpty()) { 667 return null; 668 } 669 if (DEBUG) { 670 Slog.d(TAG, "Found " + results.size() + " early events for " + names.size() + " apps"); 671 } 672 673 String[] table = names.toArray(new String[names.size()]); 674 Arrays.sort(table); 675 return new UsageEvents(results, table, false); 676 } 677 678 @Nullable queryEventsForPackage(final long beginTime, final long endTime, final String packageName, boolean includeTaskRoot)679 UsageEvents queryEventsForPackage(final long beginTime, final long endTime, 680 final String packageName, boolean includeTaskRoot) { 681 if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) { 682 return null; 683 } 684 final ArraySet<String> names = new ArraySet<>(); 685 names.add(packageName); 686 final List<Event> results = queryStats(INTERVAL_DAILY, 687 beginTime, endTime, (stats, mutable, accumulatedResult) -> { 688 final int startIndex = stats.events.firstIndexOnOrAfter(beginTime); 689 final int size = stats.events.size(); 690 for (int i = startIndex; i < size; i++) { 691 final Event event = stats.events.get(i); 692 if (event.mTimeStamp >= endTime) { 693 return false; 694 } 695 696 if (!packageName.equals(event.mPackage)) { 697 continue; 698 } 699 if (event.mClass != null) { 700 names.add(event.mClass); 701 } 702 if (includeTaskRoot && event.mTaskRootPackage != null) { 703 names.add(event.mTaskRootPackage); 704 } 705 if (includeTaskRoot && event.mTaskRootClass != null) { 706 names.add(event.mTaskRootClass); 707 } 708 accumulatedResult.add(event); 709 } 710 return true; 711 }, false); 712 713 if (results == null || results.isEmpty()) { 714 return null; 715 } 716 717 final String[] table = names.toArray(new String[names.size()]); 718 Arrays.sort(table); 719 return new UsageEvents(results, table, includeTaskRoot); 720 } 721 722 /** 723 * Returns a {@link UsageEvents} object whose events list contains only the earliest event seen 724 * for the package as well as the earliest event of {@code eventType} seen for the package. 725 */ 726 @Nullable queryEarliestEventsForPackage(long beginTime, final long endTime, @NonNull final String packageName, final int eventType)727 UsageEvents queryEarliestEventsForPackage(long beginTime, final long endTime, 728 @NonNull final String packageName, final int eventType) { 729 final long currentTime = checkAndGetTimeLocked(); 730 if (!validRange(currentTime, beginTime, endTime)) { 731 return null; 732 } 733 734 CachedEarlyEvents cachedEvents = mCachedEarlyEvents.get(eventType, packageName); 735 if (cachedEvents != null) { 736 // We can use this cached event if the previous search time was the exact same 737 // or earlier AND the event we previously found was at this current time or 738 // afterwards. Since no new events will be added before the cached event, 739 // redoing the search will yield the same event. 740 if (cachedEvents.searchBeginTime <= beginTime && beginTime <= cachedEvents.eventTime) { 741 final int numEvents = cachedEvents.events == null ? 0 : cachedEvents.events.size(); 742 if ((numEvents == 0 743 || cachedEvents.events.get(numEvents - 1).getEventType() != eventType) 744 && cachedEvents.eventTime < endTime) { 745 // We didn't find a match in the earlier range but this new request is allowing 746 // us to look at events after the previous request's end time, so we may find 747 // something new. 748 beginTime = Math.min(currentTime, cachedEvents.eventTime); 749 // Leave the cachedEvents's searchBeginTime as the earlier begin time to 750 // cache/show that we searched the entire range (the union of the two queries): 751 // [previous query's begin time, current query's end time]. 752 } else if (cachedEvents.eventTime <= endTime) { 753 if (cachedEvents.events == null) { 754 return null; 755 } 756 return new UsageEvents(cachedEvents.events, new String[]{packageName}, false); 757 } else { 758 // Any event we previously found is after the end of this query's range, but 759 // this query starts at the same time (or after) the previous query's begin time 760 // so there is no event to return. 761 return null; 762 } 763 } else { 764 // The previous query isn't helpful in any way for this query. Reset the event data. 765 cachedEvents.searchBeginTime = beginTime; 766 } 767 } else { 768 cachedEvents = new CachedEarlyEvents(); 769 cachedEvents.searchBeginTime = beginTime; 770 mCachedEarlyEvents.add(eventType, packageName, cachedEvents); 771 } 772 773 final long finalBeginTime = beginTime; 774 final List<Event> results = queryStats(INTERVAL_DAILY, 775 beginTime, endTime, (stats, mutable, accumulatedResult) -> { 776 final int startIndex = stats.events.firstIndexOnOrAfter(finalBeginTime); 777 final int size = stats.events.size(); 778 for (int i = startIndex; i < size; i++) { 779 final Event event = stats.events.get(i); 780 if (event.getTimeStamp() >= endTime) { 781 return false; 782 } 783 784 if (!packageName.equals(event.getPackageName())) { 785 continue; 786 } 787 if (event.getEventType() == eventType) { 788 accumulatedResult.add(event); 789 // We've found the earliest of eventType. No need to keep going. 790 return false; 791 } else if (accumulatedResult.size() == 0) { 792 // Save the earliest found event, even if it doesn't match. 793 accumulatedResult.add(event); 794 } 795 } 796 return true; 797 }, false); 798 799 if (results == null || results.isEmpty()) { 800 // There won't be any new events added earlier than endTime, so we can use endTime to 801 // avoid querying for events earlier than it. 802 cachedEvents.eventTime = Math.min(currentTime, endTime); 803 cachedEvents.events = null; 804 return null; 805 } 806 807 cachedEvents.eventTime = results.get(results.size() - 1).getTimeStamp(); 808 cachedEvents.events = results; 809 return new UsageEvents(results, new String[]{packageName}, false); 810 } 811 persistActiveStats()812 void persistActiveStats() { 813 if (mStatsChanged) { 814 Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk"); 815 try { 816 mDatabase.obfuscateCurrentStats(mCurrentStats); 817 mDatabase.writeMappingsLocked(); 818 for (int i = 0; i < mCurrentStats.length; i++) { 819 mDatabase.putUsageStats(i, mCurrentStats[i]); 820 } 821 mStatsChanged = false; 822 } catch (IOException e) { 823 Slog.e(TAG, mLogPrefix + "Failed to persist active stats", e); 824 } 825 } 826 } 827 rolloverStats(final long currentTimeMillis)828 private void rolloverStats(final long currentTimeMillis) { 829 final long startTime = SystemClock.elapsedRealtime(); 830 Slog.i(TAG, mLogPrefix + "Rolling over usage stats"); 831 832 // Finish any ongoing events with an END_OF_DAY or ROLLOVER_FOREGROUND_SERVICE event. 833 // Make a note of which components need a new CONTINUE_PREVIOUS_DAY or 834 // CONTINUING_FOREGROUND_SERVICE entry. 835 final Configuration previousConfig = 836 mCurrentStats[INTERVAL_DAILY].activeConfiguration; 837 ArraySet<String> continuePkgs = new ArraySet<>(); 838 ArrayMap<String, SparseIntArray> continueActivity = 839 new ArrayMap<>(); 840 ArrayMap<String, ArrayMap<String, Integer>> continueForegroundService = 841 new ArrayMap<>(); 842 for (IntervalStats stat : mCurrentStats) { 843 final int pkgCount = stat.packageStats.size(); 844 for (int i = 0; i < pkgCount; i++) { 845 final UsageStats pkgStats = stat.packageStats.valueAt(i); 846 if (pkgStats.mActivities.size() > 0 847 || !pkgStats.mForegroundServices.isEmpty()) { 848 if (pkgStats.mActivities.size() > 0) { 849 continueActivity.put(pkgStats.mPackageName, 850 pkgStats.mActivities); 851 stat.update(pkgStats.mPackageName, null, 852 mDailyExpiryDate.getTimeInMillis() - 1, 853 Event.END_OF_DAY, 0); 854 } 855 if (!pkgStats.mForegroundServices.isEmpty()) { 856 continueForegroundService.put(pkgStats.mPackageName, 857 pkgStats.mForegroundServices); 858 stat.update(pkgStats.mPackageName, null, 859 mDailyExpiryDate.getTimeInMillis() - 1, 860 Event.ROLLOVER_FOREGROUND_SERVICE, 0); 861 } 862 continuePkgs.add(pkgStats.mPackageName); 863 notifyStatsChanged(); 864 } 865 } 866 867 stat.updateConfigurationStats(null, 868 mDailyExpiryDate.getTimeInMillis() - 1); 869 stat.commitTime(mDailyExpiryDate.getTimeInMillis() - 1); 870 } 871 872 persistActiveStats(); 873 mDatabase.prune(currentTimeMillis); 874 loadActiveStats(currentTimeMillis); 875 876 final int continueCount = continuePkgs.size(); 877 for (int i = 0; i < continueCount; i++) { 878 String pkgName = continuePkgs.valueAt(i); 879 final long beginTime = mCurrentStats[INTERVAL_DAILY].beginTime; 880 for (IntervalStats stat : mCurrentStats) { 881 if (continueActivity.containsKey(pkgName)) { 882 final SparseIntArray eventMap = 883 continueActivity.get(pkgName); 884 final int size = eventMap.size(); 885 for (int j = 0; j < size; j++) { 886 stat.update(pkgName, null, beginTime, 887 eventMap.valueAt(j), eventMap.keyAt(j)); 888 } 889 } 890 if (continueForegroundService.containsKey(pkgName)) { 891 final ArrayMap<String, Integer> eventMap = 892 continueForegroundService.get(pkgName); 893 final int size = eventMap.size(); 894 for (int j = 0; j < size; j++) { 895 stat.update(pkgName, eventMap.keyAt(j), beginTime, 896 eventMap.valueAt(j), 0); 897 } 898 } 899 stat.updateConfigurationStats(previousConfig, beginTime); 900 notifyStatsChanged(); 901 } 902 } 903 persistActiveStats(); 904 905 final long totalTime = SystemClock.elapsedRealtime() - startTime; 906 Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime 907 + " milliseconds"); 908 } 909 notifyStatsChanged()910 private void notifyStatsChanged() { 911 if (!mStatsChanged) { 912 mStatsChanged = true; 913 mListener.onStatsUpdated(); 914 } 915 } 916 notifyNewUpdate()917 private void notifyNewUpdate() { 918 mListener.onNewUpdate(mUserId); 919 } 920 loadActiveStats(final long currentTimeMillis)921 private void loadActiveStats(final long currentTimeMillis) { 922 for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) { 923 final IntervalStats stats = mDatabase.getLatestUsageStats(intervalType); 924 if (stats != null 925 && currentTimeMillis < stats.beginTime + INTERVAL_LENGTH[intervalType]) { 926 if (DEBUG) { 927 Slog.d(TAG, mLogPrefix + "Loading existing stats @ " + 928 sDateFormat.format(stats.beginTime) + "(" + stats.beginTime + 929 ") for interval " + intervalType); 930 } 931 mCurrentStats[intervalType] = stats; 932 } else { 933 // No good fit remains. 934 if (DEBUG) { 935 Slog.d(TAG, "Creating new stats @ " + 936 sDateFormat.format(currentTimeMillis) + "(" + 937 currentTimeMillis + ") for interval " + intervalType); 938 } 939 940 mCurrentStats[intervalType] = new IntervalStats(); 941 mCurrentStats[intervalType].beginTime = currentTimeMillis; 942 mCurrentStats[intervalType].endTime = currentTimeMillis + 1; 943 } 944 } 945 946 mStatsChanged = false; 947 updateRolloverDeadline(); 948 949 // Tell the listener that the stats reloaded, which may have changed idle states. 950 mListener.onStatsReloaded(); 951 } 952 updateRolloverDeadline()953 private void updateRolloverDeadline() { 954 mDailyExpiryDate.setTimeInMillis( 955 mCurrentStats[INTERVAL_DAILY].beginTime); 956 mDailyExpiryDate.addDays(1); 957 Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " + 958 sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" + 959 mDailyExpiryDate.getTimeInMillis() + ")"); 960 } 961 962 // 963 // -- DUMP related methods -- 964 // 965 checkin(final IndentingPrintWriter pw)966 void checkin(final IndentingPrintWriter pw) { 967 mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() { 968 @Override 969 public boolean checkin(IntervalStats stats) { 970 printIntervalStats(pw, stats, false, false, null); 971 return true; 972 } 973 }); 974 } 975 dump(IndentingPrintWriter pw, List<String> pkgs)976 void dump(IndentingPrintWriter pw, List<String> pkgs) { 977 dump(pw, pkgs, false); 978 } 979 dump(IndentingPrintWriter pw, List<String> pkgs, boolean compact)980 void dump(IndentingPrintWriter pw, List<String> pkgs, boolean compact) { 981 printLast24HrEvents(pw, !compact, pkgs); 982 for (int interval = 0; interval < mCurrentStats.length; interval++) { 983 pw.print("In-memory "); 984 pw.print(intervalToString(interval)); 985 pw.println(" stats"); 986 printIntervalStats(pw, mCurrentStats[interval], !compact, true, pkgs); 987 } 988 if (CollectionUtils.isEmpty(pkgs)) { 989 mDatabase.dump(pw, compact); 990 } 991 } 992 dumpDatabaseInfo(IndentingPrintWriter ipw)993 void dumpDatabaseInfo(IndentingPrintWriter ipw) { 994 mDatabase.dump(ipw, false); 995 } 996 dumpMappings(IndentingPrintWriter ipw)997 void dumpMappings(IndentingPrintWriter ipw) { 998 mDatabase.dumpMappings(ipw); 999 } 1000 deleteDataFor(String pkg)1001 void deleteDataFor(String pkg) { 1002 mDatabase.deleteDataFor(pkg); 1003 } 1004 dumpFile(IndentingPrintWriter ipw, String[] args)1005 void dumpFile(IndentingPrintWriter ipw, String[] args) { 1006 if (args == null || args.length == 0) { 1007 // dump all files for every interval for specified user 1008 final int numIntervals = mDatabase.mSortedStatFiles.length; 1009 for (int interval = 0; interval < numIntervals; interval++) { 1010 ipw.println("interval=" + intervalToString(interval)); 1011 ipw.increaseIndent(); 1012 dumpFileDetailsForInterval(ipw, interval); 1013 ipw.decreaseIndent(); 1014 } 1015 } else { 1016 final int interval; 1017 try { 1018 final int intervalValue = stringToInterval(args[0]); 1019 if (intervalValue == -1) { 1020 interval = Integer.valueOf(args[0]); 1021 } else { 1022 interval = intervalValue; 1023 } 1024 } catch (NumberFormatException nfe) { 1025 ipw.println("invalid interval specified."); 1026 return; 1027 } 1028 if (interval < 0 || interval >= mDatabase.mSortedStatFiles.length) { 1029 ipw.println("the specified interval does not exist."); 1030 return; 1031 } 1032 if (args.length == 1) { 1033 // dump all files in the specified interval 1034 dumpFileDetailsForInterval(ipw, interval); 1035 } else { 1036 // dump details only for the specified filename 1037 final long filename; 1038 try { 1039 filename = Long.valueOf(args[1]); 1040 } catch (NumberFormatException nfe) { 1041 ipw.println("invalid filename specified."); 1042 return; 1043 } 1044 final IntervalStats stats = mDatabase.readIntervalStatsForFile(interval, filename); 1045 if (stats == null) { 1046 ipw.println("the specified filename does not exist."); 1047 return; 1048 } 1049 dumpFileDetails(ipw, stats, Long.valueOf(args[1])); 1050 } 1051 } 1052 } 1053 dumpFileDetailsForInterval(IndentingPrintWriter ipw, int interval)1054 private void dumpFileDetailsForInterval(IndentingPrintWriter ipw, int interval) { 1055 final LongSparseArray<AtomicFile> files = mDatabase.mSortedStatFiles[interval]; 1056 final int numFiles = files.size(); 1057 for (int i = 0; i < numFiles; i++) { 1058 final long filename = files.keyAt(i); 1059 final IntervalStats stats = mDatabase.readIntervalStatsForFile(interval, filename); 1060 dumpFileDetails(ipw, stats, filename); 1061 ipw.println(); 1062 } 1063 } 1064 dumpFileDetails(IndentingPrintWriter ipw, IntervalStats stats, long filename)1065 private void dumpFileDetails(IndentingPrintWriter ipw, IntervalStats stats, long filename) { 1066 ipw.println("file=" + filename); 1067 ipw.increaseIndent(); 1068 printIntervalStats(ipw, stats, false, false, null); 1069 ipw.decreaseIndent(); 1070 } 1071 formatDateTime(long dateTime, boolean pretty)1072 static String formatDateTime(long dateTime, boolean pretty) { 1073 if (pretty) { 1074 return "\"" + sDateFormat.format(dateTime)+ "\""; 1075 } 1076 return Long.toString(dateTime); 1077 } 1078 formatElapsedTime(long elapsedTime, boolean pretty)1079 private String formatElapsedTime(long elapsedTime, boolean pretty) { 1080 if (pretty) { 1081 return "\"" + DateUtils.formatElapsedTime(elapsedTime / 1000) + "\""; 1082 } 1083 return Long.toString(elapsedTime); 1084 } 1085 printEvent(IndentingPrintWriter pw, Event event, boolean prettyDates)1086 static void printEvent(IndentingPrintWriter pw, Event event, boolean prettyDates) { 1087 pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates)); 1088 pw.printPair("type", eventToString(event.mEventType)); 1089 pw.printPair("package", event.mPackage); 1090 if (event.mClass != null) { 1091 pw.printPair("class", event.mClass); 1092 } 1093 if (event.mConfiguration != null) { 1094 pw.printPair("config", Configuration.resourceQualifierString(event.mConfiguration)); 1095 } 1096 if (event.mShortcutId != null) { 1097 pw.printPair("shortcutId", event.mShortcutId); 1098 } 1099 if (event.mEventType == Event.STANDBY_BUCKET_CHANGED) { 1100 pw.printPair("standbyBucket", event.getAppStandbyBucket()); 1101 pw.printPair("reason", UsageStatsManager.reasonToString(event.getStandbyReason())); 1102 } else if (event.mEventType == Event.ACTIVITY_RESUMED 1103 || event.mEventType == Event.ACTIVITY_PAUSED 1104 || event.mEventType == Event.ACTIVITY_STOPPED) { 1105 pw.printPair("instanceId", event.getInstanceId()); 1106 } 1107 1108 if (event.getTaskRootPackageName() != null) { 1109 pw.printPair("taskRootPackage", event.getTaskRootPackageName()); 1110 } 1111 1112 if (event.getTaskRootClassName() != null) { 1113 pw.printPair("taskRootClass", event.getTaskRootClassName()); 1114 } 1115 1116 if (event.mNotificationChannelId != null) { 1117 pw.printPair("channelId", event.mNotificationChannelId); 1118 } 1119 1120 if ((event.mEventType == Event.USER_INTERACTION) && (event.mExtras != null)) { 1121 pw.print(event.mExtras.toString()); 1122 } 1123 pw.printHexPair("flags", event.mFlags); 1124 pw.println(); 1125 } 1126 printLast24HrEvents(IndentingPrintWriter pw, boolean prettyDates, final List<String> pkgs)1127 void printLast24HrEvents(IndentingPrintWriter pw, boolean prettyDates, 1128 final List<String> pkgs) { 1129 final long endTime = System.currentTimeMillis(); 1130 UnixCalendar yesterday = new UnixCalendar(endTime); 1131 yesterday.addDays(-1); 1132 1133 final long beginTime = yesterday.getTimeInMillis(); 1134 1135 List<Event> events = queryStats(INTERVAL_DAILY, 1136 beginTime, endTime, new StatCombiner<Event>() { 1137 @Override 1138 public boolean combine(IntervalStats stats, boolean mutable, 1139 List<Event> accumulatedResult) { 1140 final int startIndex = stats.events.firstIndexOnOrAfter(beginTime); 1141 final int size = stats.events.size(); 1142 for (int i = startIndex; i < size; i++) { 1143 if (stats.events.get(i).mTimeStamp >= endTime) { 1144 return false; 1145 } 1146 1147 Event event = stats.events.get(i); 1148 if (!CollectionUtils.isEmpty(pkgs) && !pkgs.contains(event.mPackage)) { 1149 continue; 1150 } 1151 accumulatedResult.add(event); 1152 } 1153 return true; 1154 } 1155 }, false); 1156 1157 pw.print("Last 24 hour events ("); 1158 if (prettyDates) { 1159 pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext, 1160 beginTime, endTime, sDateFormatFlags) + "\""); 1161 } else { 1162 pw.printPair("beginTime", beginTime); 1163 pw.printPair("endTime", endTime); 1164 } 1165 pw.println(")"); 1166 if (events != null) { 1167 pw.increaseIndent(); 1168 for (Event event : events) { 1169 printEvent(pw, event, prettyDates); 1170 } 1171 pw.decreaseIndent(); 1172 } 1173 } 1174 printEventAggregation(IndentingPrintWriter pw, String label, IntervalStats.EventTracker tracker, boolean prettyDates)1175 void printEventAggregation(IndentingPrintWriter pw, String label, 1176 IntervalStats.EventTracker tracker, boolean prettyDates) { 1177 if (tracker.count != 0 || tracker.duration != 0) { 1178 pw.print(label); 1179 pw.print(": "); 1180 pw.print(tracker.count); 1181 pw.print("x for "); 1182 pw.print(formatElapsedTime(tracker.duration, prettyDates)); 1183 if (tracker.curStartTime != 0) { 1184 pw.print(" (now running, started at "); 1185 formatDateTime(tracker.curStartTime, prettyDates); 1186 pw.print(")"); 1187 } 1188 pw.println(); 1189 } 1190 } 1191 printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, boolean prettyDates, boolean skipEvents, List<String> pkgs)1192 void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, 1193 boolean prettyDates, boolean skipEvents, List<String> pkgs) { 1194 if (prettyDates) { 1195 pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext, 1196 stats.beginTime, stats.endTime, sDateFormatFlags) + "\""); 1197 } else { 1198 pw.printPair("beginTime", stats.beginTime); 1199 pw.printPair("endTime", stats.endTime); 1200 } 1201 pw.println(); 1202 pw.increaseIndent(); 1203 pw.println("packages"); 1204 pw.increaseIndent(); 1205 final ArrayMap<String, UsageStats> pkgStats = stats.packageStats; 1206 final int pkgCount = pkgStats.size(); 1207 for (int i = 0; i < pkgCount; i++) { 1208 final UsageStats usageStats = pkgStats.valueAt(i); 1209 if (!CollectionUtils.isEmpty(pkgs) && !pkgs.contains(usageStats.mPackageName)) { 1210 continue; 1211 } 1212 pw.printPair("package", usageStats.mPackageName); 1213 pw.printPair("totalTimeUsed", 1214 formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates)); 1215 pw.printPair("lastTimeUsed", formatDateTime(usageStats.mLastTimeUsed, prettyDates)); 1216 pw.printPair("totalTimeVisible", 1217 formatElapsedTime(usageStats.mTotalTimeVisible, prettyDates)); 1218 pw.printPair("lastTimeVisible", 1219 formatDateTime(usageStats.mLastTimeVisible, prettyDates)); 1220 pw.printPair("lastTimeComponentUsed", 1221 formatDateTime(usageStats.mLastTimeComponentUsed, prettyDates)); 1222 pw.printPair("totalTimeFS", 1223 formatElapsedTime(usageStats.mTotalTimeForegroundServiceUsed, prettyDates)); 1224 pw.printPair("lastTimeFS", 1225 formatDateTime(usageStats.mLastTimeForegroundServiceUsed, prettyDates)); 1226 pw.printPair("appLaunchCount", usageStats.mAppLaunchCount); 1227 pw.println(); 1228 } 1229 pw.decreaseIndent(); 1230 1231 pw.println(); 1232 pw.println("ChooserCounts"); 1233 pw.increaseIndent(); 1234 for (UsageStats usageStats : pkgStats.values()) { 1235 if (!CollectionUtils.isEmpty(pkgs) && !pkgs.contains(usageStats.mPackageName)) { 1236 continue; 1237 } 1238 pw.printPair("package", usageStats.mPackageName); 1239 if (usageStats.mChooserCounts != null) { 1240 final int chooserCountSize = usageStats.mChooserCounts.size(); 1241 for (int i = 0; i < chooserCountSize; i++) { 1242 final String action = usageStats.mChooserCounts.keyAt(i); 1243 final ArrayMap<String, Integer> counts = usageStats.mChooserCounts.valueAt(i); 1244 final int annotationSize = counts.size(); 1245 for (int j = 0; j < annotationSize; j++) { 1246 final String key = counts.keyAt(j); 1247 final int count = counts.valueAt(j); 1248 if (count != 0) { 1249 pw.printPair("ChooserCounts", action + ":" + key + " is " + 1250 Integer.toString(count)); 1251 pw.println(); 1252 } 1253 } 1254 } 1255 } 1256 pw.println(); 1257 } 1258 pw.decreaseIndent(); 1259 1260 if (CollectionUtils.isEmpty(pkgs)) { 1261 pw.println("configurations"); 1262 pw.increaseIndent(); 1263 final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations; 1264 final int configCount = configStats.size(); 1265 for (int i = 0; i < configCount; i++) { 1266 final ConfigurationStats config = configStats.valueAt(i); 1267 pw.printPair("config", Configuration.resourceQualifierString( 1268 config.mConfiguration)); 1269 pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates)); 1270 pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates)); 1271 pw.printPair("count", config.mActivationCount); 1272 pw.println(); 1273 } 1274 pw.decreaseIndent(); 1275 pw.println("event aggregations"); 1276 pw.increaseIndent(); 1277 printEventAggregation(pw, "screen-interactive", stats.interactiveTracker, 1278 prettyDates); 1279 printEventAggregation(pw, "screen-non-interactive", stats.nonInteractiveTracker, 1280 prettyDates); 1281 printEventAggregation(pw, "keyguard-shown", stats.keyguardShownTracker, 1282 prettyDates); 1283 printEventAggregation(pw, "keyguard-hidden", stats.keyguardHiddenTracker, 1284 prettyDates); 1285 pw.decreaseIndent(); 1286 } 1287 1288 // The last 24 hours of events is already printed in the non checkin dump 1289 // No need to repeat here. 1290 if (!skipEvents) { 1291 pw.println("events"); 1292 pw.increaseIndent(); 1293 final EventList events = stats.events; 1294 final int eventCount = events != null ? events.size() : 0; 1295 for (int i = 0; i < eventCount; i++) { 1296 final Event event = events.get(i); 1297 if (!CollectionUtils.isEmpty(pkgs) && !pkgs.contains(event.mPackage)) { 1298 continue; 1299 } 1300 printEvent(pw, event, prettyDates); 1301 } 1302 pw.decreaseIndent(); 1303 } 1304 pw.decreaseIndent(); 1305 } 1306 intervalToString(int interval)1307 public static String intervalToString(int interval) { 1308 switch (interval) { 1309 case INTERVAL_DAILY: 1310 return "daily"; 1311 case INTERVAL_WEEKLY: 1312 return "weekly"; 1313 case INTERVAL_MONTHLY: 1314 return "monthly"; 1315 case INTERVAL_YEARLY: 1316 return "yearly"; 1317 default: 1318 return "?"; 1319 } 1320 } 1321 stringToInterval(String interval)1322 private static int stringToInterval(String interval) { 1323 switch (interval.toLowerCase()) { 1324 case "daily": 1325 return INTERVAL_DAILY; 1326 case "weekly": 1327 return INTERVAL_WEEKLY; 1328 case "monthly": 1329 return INTERVAL_MONTHLY; 1330 case "yearly": 1331 return INTERVAL_YEARLY; 1332 default: 1333 return -1; 1334 } 1335 } 1336 eventToString(int eventType)1337 static String eventToString(int eventType) { 1338 switch (eventType) { 1339 case Event.NONE: 1340 return "NONE"; 1341 case Event.ACTIVITY_PAUSED: 1342 return "ACTIVITY_PAUSED"; 1343 case Event.ACTIVITY_RESUMED: 1344 return "ACTIVITY_RESUMED"; 1345 case Event.FOREGROUND_SERVICE_START: 1346 return "FOREGROUND_SERVICE_START"; 1347 case Event.FOREGROUND_SERVICE_STOP: 1348 return "FOREGROUND_SERVICE_STOP"; 1349 case Event.ACTIVITY_STOPPED: 1350 return "ACTIVITY_STOPPED"; 1351 case Event.END_OF_DAY: 1352 return "END_OF_DAY"; 1353 case Event.ROLLOVER_FOREGROUND_SERVICE: 1354 return "ROLLOVER_FOREGROUND_SERVICE"; 1355 case Event.CONTINUE_PREVIOUS_DAY: 1356 return "CONTINUE_PREVIOUS_DAY"; 1357 case Event.CONTINUING_FOREGROUND_SERVICE: 1358 return "CONTINUING_FOREGROUND_SERVICE"; 1359 case Event.CONFIGURATION_CHANGE: 1360 return "CONFIGURATION_CHANGE"; 1361 case Event.SYSTEM_INTERACTION: 1362 return "SYSTEM_INTERACTION"; 1363 case Event.USER_INTERACTION: 1364 return "USER_INTERACTION"; 1365 case Event.SHORTCUT_INVOCATION: 1366 return "SHORTCUT_INVOCATION"; 1367 case Event.CHOOSER_ACTION: 1368 return "CHOOSER_ACTION"; 1369 case Event.NOTIFICATION_SEEN: 1370 return "NOTIFICATION_SEEN"; 1371 case Event.STANDBY_BUCKET_CHANGED: 1372 return "STANDBY_BUCKET_CHANGED"; 1373 case Event.NOTIFICATION_INTERRUPTION: 1374 return "NOTIFICATION_INTERRUPTION"; 1375 case Event.SLICE_PINNED: 1376 return "SLICE_PINNED"; 1377 case Event.SLICE_PINNED_PRIV: 1378 return "SLICE_PINNED_PRIV"; 1379 case Event.SCREEN_INTERACTIVE: 1380 return "SCREEN_INTERACTIVE"; 1381 case Event.SCREEN_NON_INTERACTIVE: 1382 return "SCREEN_NON_INTERACTIVE"; 1383 case Event.KEYGUARD_SHOWN: 1384 return "KEYGUARD_SHOWN"; 1385 case Event.KEYGUARD_HIDDEN: 1386 return "KEYGUARD_HIDDEN"; 1387 case Event.DEVICE_SHUTDOWN: 1388 return "DEVICE_SHUTDOWN"; 1389 case Event.DEVICE_STARTUP: 1390 return "DEVICE_STARTUP"; 1391 case Event.USER_UNLOCKED: 1392 return "USER_UNLOCKED"; 1393 case Event.USER_STOPPED: 1394 return "USER_STOPPED"; 1395 case Event.LOCUS_ID_SET: 1396 return "LOCUS_ID_SET"; 1397 case Event.APP_COMPONENT_USED: 1398 return "APP_COMPONENT_USED"; 1399 default: 1400 return "UNKNOWN_TYPE_" + eventType; 1401 } 1402 } 1403 getBackupPayload(String key)1404 byte[] getBackupPayload(String key){ 1405 checkAndGetTimeLocked(); 1406 persistActiveStats(); 1407 return mDatabase.getBackupPayload(key); 1408 } 1409 applyRestoredPayload(String key, byte[] payload)1410 Set<String> applyRestoredPayload(String key, byte[] payload) { 1411 checkAndGetTimeLocked(); 1412 return mDatabase.applyRestoredPayload(key, payload); 1413 } 1414 } 1415