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.Slog; 50 import android.util.SparseArrayMap; 51 import android.util.SparseIntArray; 52 import android.util.TimeSparseArray; 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"; 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)450 private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime, 451 StatCombiner<T> combiner) { 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); 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); 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); 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); 536 } 537 queryEvents(final long beginTime, final long endTime, int flags)538 UsageEvents queryEvents(final long beginTime, final long endTime, int flags) { 539 if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) { 540 return null; 541 } 542 final ArraySet<String> names = new ArraySet<>(); 543 List<Event> results = queryStats(INTERVAL_DAILY, 544 beginTime, endTime, new StatCombiner<Event>() { 545 @Override 546 public boolean combine(IntervalStats stats, boolean mutable, 547 List<Event> accumulatedResult) { 548 final int startIndex = stats.events.firstIndexOnOrAfter(beginTime); 549 final int size = stats.events.size(); 550 for (int i = startIndex; i < size; i++) { 551 Event event = stats.events.get(i); 552 if (event.mTimeStamp >= endTime) { 553 return false; 554 } 555 556 final int eventType = event.mEventType; 557 if (eventType == Event.SHORTCUT_INVOCATION 558 && (flags & HIDE_SHORTCUT_EVENTS) == HIDE_SHORTCUT_EVENTS) { 559 continue; 560 } 561 if (eventType == Event.LOCUS_ID_SET 562 && (flags & HIDE_LOCUS_EVENTS) == HIDE_LOCUS_EVENTS) { 563 continue; 564 } 565 if ((eventType == Event.NOTIFICATION_SEEN 566 || eventType == Event.NOTIFICATION_INTERRUPTION) 567 && (flags & OBFUSCATE_NOTIFICATION_EVENTS) 568 == OBFUSCATE_NOTIFICATION_EVENTS) { 569 event = event.getObfuscatedNotificationEvent(); 570 } 571 if ((flags & OBFUSCATE_INSTANT_APPS) == OBFUSCATE_INSTANT_APPS) { 572 event = event.getObfuscatedIfInstantApp(); 573 } 574 if (event.mPackage != null) { 575 names.add(event.mPackage); 576 } 577 if (event.mClass != null) { 578 names.add(event.mClass); 579 } 580 if (event.mTaskRootPackage != null) { 581 names.add(event.mTaskRootPackage); 582 } 583 if (event.mTaskRootClass != null) { 584 names.add(event.mTaskRootClass); 585 } 586 accumulatedResult.add(event); 587 } 588 return true; 589 } 590 }); 591 592 if (results == null || results.isEmpty()) { 593 return null; 594 } 595 596 String[] table = names.toArray(new String[names.size()]); 597 Arrays.sort(table); 598 return new UsageEvents(results, table, true); 599 } 600 601 /** 602 * Returns a {@link UsageEvents} object whose events list contains only the earliest event seen 603 * for each app as well as the earliest event of {@code eventType} seen for each app. 604 */ 605 @Nullable queryEarliestAppEvents(final long beginTime, final long endTime, final int eventType)606 UsageEvents queryEarliestAppEvents(final long beginTime, final long endTime, 607 final int eventType) { 608 if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) { 609 return null; 610 } 611 final ArraySet<String> names = new ArraySet<>(); 612 final ArraySet<String> eventSuccess = new ArraySet<>(); 613 final List<Event> results = queryStats(INTERVAL_DAILY, 614 beginTime, endTime, (stats, mutable, accumulatedResult) -> { 615 final int startIndex = stats.events.firstIndexOnOrAfter(beginTime); 616 final int size = stats.events.size(); 617 for (int i = startIndex; i < size; i++) { 618 final Event event = stats.events.get(i); 619 if (event.getTimeStamp() >= endTime) { 620 return false; 621 } 622 if (event.getPackageName() == null) { 623 continue; 624 } 625 if (eventSuccess.contains(event.getPackageName())) { 626 continue; 627 } 628 629 final boolean firstEvent = names.add(event.getPackageName()); 630 631 if (event.getEventType() == eventType) { 632 accumulatedResult.add(event); 633 eventSuccess.add(event.getPackageName()); 634 } else if (firstEvent) { 635 // Save the earliest found event for the app, even if it doesn't match. 636 accumulatedResult.add(event); 637 } 638 } 639 return true; 640 }); 641 642 if (results == null || results.isEmpty()) { 643 return null; 644 } 645 if (DEBUG) { 646 Slog.d(TAG, "Found " + results.size() + " early events for " + names.size() + " apps"); 647 } 648 649 String[] table = names.toArray(new String[names.size()]); 650 Arrays.sort(table); 651 return new UsageEvents(results, table, false); 652 } 653 654 @Nullable queryEventsForPackage(final long beginTime, final long endTime, final String packageName, boolean includeTaskRoot)655 UsageEvents queryEventsForPackage(final long beginTime, final long endTime, 656 final String packageName, boolean includeTaskRoot) { 657 if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) { 658 return null; 659 } 660 final ArraySet<String> names = new ArraySet<>(); 661 names.add(packageName); 662 final List<Event> results = queryStats(INTERVAL_DAILY, 663 beginTime, endTime, (stats, mutable, accumulatedResult) -> { 664 final int startIndex = stats.events.firstIndexOnOrAfter(beginTime); 665 final int size = stats.events.size(); 666 for (int i = startIndex; i < size; i++) { 667 final Event event = stats.events.get(i); 668 if (event.mTimeStamp >= endTime) { 669 return false; 670 } 671 672 if (!packageName.equals(event.mPackage)) { 673 continue; 674 } 675 if (event.mClass != null) { 676 names.add(event.mClass); 677 } 678 if (includeTaskRoot && event.mTaskRootPackage != null) { 679 names.add(event.mTaskRootPackage); 680 } 681 if (includeTaskRoot && event.mTaskRootClass != null) { 682 names.add(event.mTaskRootClass); 683 } 684 accumulatedResult.add(event); 685 } 686 return true; 687 }); 688 689 if (results == null || results.isEmpty()) { 690 return null; 691 } 692 693 final String[] table = names.toArray(new String[names.size()]); 694 Arrays.sort(table); 695 return new UsageEvents(results, table, includeTaskRoot); 696 } 697 698 /** 699 * Returns a {@link UsageEvents} object whose events list contains only the earliest event seen 700 * for the package as well as the earliest event of {@code eventType} seen for the package. 701 */ 702 @Nullable queryEarliestEventsForPackage(long beginTime, final long endTime, @NonNull final String packageName, final int eventType)703 UsageEvents queryEarliestEventsForPackage(long beginTime, final long endTime, 704 @NonNull final String packageName, final int eventType) { 705 final long currentTime = checkAndGetTimeLocked(); 706 if (!validRange(currentTime, beginTime, endTime)) { 707 return null; 708 } 709 710 CachedEarlyEvents cachedEvents = mCachedEarlyEvents.get(eventType, packageName); 711 if (cachedEvents != null) { 712 // We can use this cached event if the previous search time was the exact same 713 // or earlier AND the event we previously found was at this current time or 714 // afterwards. Since no new events will be added before the cached event, 715 // redoing the search will yield the same event. 716 if (cachedEvents.searchBeginTime <= beginTime && beginTime <= cachedEvents.eventTime) { 717 final int numEvents = cachedEvents.events == null ? 0 : cachedEvents.events.size(); 718 if ((numEvents == 0 719 || cachedEvents.events.get(numEvents - 1).getEventType() != eventType) 720 && cachedEvents.eventTime < endTime) { 721 // We didn't find a match in the earlier range but this new request is allowing 722 // us to look at events after the previous request's end time, so we may find 723 // something new. 724 beginTime = Math.min(currentTime, cachedEvents.eventTime); 725 // Leave the cachedEvents's searchBeginTime as the earlier begin time to 726 // cache/show that we searched the entire range (the union of the two queries): 727 // [previous query's begin time, current query's end time]. 728 } else if (cachedEvents.eventTime <= endTime) { 729 if (cachedEvents.events == null) { 730 return null; 731 } 732 return new UsageEvents(cachedEvents.events, new String[]{packageName}, false); 733 } else { 734 // Any event we previously found is after the end of this query's range, but 735 // this query starts at the same time (or after) the previous query's begin time 736 // so there is no event to return. 737 return null; 738 } 739 } else { 740 // The previous query isn't helpful in any way for this query. Reset the event data. 741 cachedEvents.searchBeginTime = beginTime; 742 } 743 } else { 744 cachedEvents = new CachedEarlyEvents(); 745 cachedEvents.searchBeginTime = beginTime; 746 mCachedEarlyEvents.add(eventType, packageName, cachedEvents); 747 } 748 749 final long finalBeginTime = beginTime; 750 final List<Event> results = queryStats(INTERVAL_DAILY, 751 beginTime, endTime, (stats, mutable, accumulatedResult) -> { 752 final int startIndex = stats.events.firstIndexOnOrAfter(finalBeginTime); 753 final int size = stats.events.size(); 754 for (int i = startIndex; i < size; i++) { 755 final Event event = stats.events.get(i); 756 if (event.getTimeStamp() >= endTime) { 757 return false; 758 } 759 760 if (!packageName.equals(event.getPackageName())) { 761 continue; 762 } 763 if (event.getEventType() == eventType) { 764 accumulatedResult.add(event); 765 // We've found the earliest of eventType. No need to keep going. 766 return false; 767 } else if (accumulatedResult.size() == 0) { 768 // Save the earliest found event, even if it doesn't match. 769 accumulatedResult.add(event); 770 } 771 } 772 return true; 773 }); 774 775 if (results == null || results.isEmpty()) { 776 // There won't be any new events added earlier than endTime, so we can use endTime to 777 // avoid querying for events earlier than it. 778 cachedEvents.eventTime = Math.min(currentTime, endTime); 779 cachedEvents.events = null; 780 return null; 781 } 782 783 cachedEvents.eventTime = results.get(results.size() - 1).getTimeStamp(); 784 cachedEvents.events = results; 785 return new UsageEvents(results, new String[]{packageName}, false); 786 } 787 persistActiveStats()788 void persistActiveStats() { 789 if (mStatsChanged) { 790 Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk"); 791 try { 792 mDatabase.obfuscateCurrentStats(mCurrentStats); 793 mDatabase.writeMappingsLocked(); 794 for (int i = 0; i < mCurrentStats.length; i++) { 795 mDatabase.putUsageStats(i, mCurrentStats[i]); 796 } 797 mStatsChanged = false; 798 } catch (IOException e) { 799 Slog.e(TAG, mLogPrefix + "Failed to persist active stats", e); 800 } 801 } 802 } 803 rolloverStats(final long currentTimeMillis)804 private void rolloverStats(final long currentTimeMillis) { 805 final long startTime = SystemClock.elapsedRealtime(); 806 Slog.i(TAG, mLogPrefix + "Rolling over usage stats"); 807 808 // Finish any ongoing events with an END_OF_DAY or ROLLOVER_FOREGROUND_SERVICE event. 809 // Make a note of which components need a new CONTINUE_PREVIOUS_DAY or 810 // CONTINUING_FOREGROUND_SERVICE entry. 811 final Configuration previousConfig = 812 mCurrentStats[INTERVAL_DAILY].activeConfiguration; 813 ArraySet<String> continuePkgs = new ArraySet<>(); 814 ArrayMap<String, SparseIntArray> continueActivity = 815 new ArrayMap<>(); 816 ArrayMap<String, ArrayMap<String, Integer>> continueForegroundService = 817 new ArrayMap<>(); 818 for (IntervalStats stat : mCurrentStats) { 819 final int pkgCount = stat.packageStats.size(); 820 for (int i = 0; i < pkgCount; i++) { 821 final UsageStats pkgStats = stat.packageStats.valueAt(i); 822 if (pkgStats.mActivities.size() > 0 823 || !pkgStats.mForegroundServices.isEmpty()) { 824 if (pkgStats.mActivities.size() > 0) { 825 continueActivity.put(pkgStats.mPackageName, 826 pkgStats.mActivities); 827 stat.update(pkgStats.mPackageName, null, 828 mDailyExpiryDate.getTimeInMillis() - 1, 829 Event.END_OF_DAY, 0); 830 } 831 if (!pkgStats.mForegroundServices.isEmpty()) { 832 continueForegroundService.put(pkgStats.mPackageName, 833 pkgStats.mForegroundServices); 834 stat.update(pkgStats.mPackageName, null, 835 mDailyExpiryDate.getTimeInMillis() - 1, 836 Event.ROLLOVER_FOREGROUND_SERVICE, 0); 837 } 838 continuePkgs.add(pkgStats.mPackageName); 839 notifyStatsChanged(); 840 } 841 } 842 843 stat.updateConfigurationStats(null, 844 mDailyExpiryDate.getTimeInMillis() - 1); 845 stat.commitTime(mDailyExpiryDate.getTimeInMillis() - 1); 846 } 847 848 persistActiveStats(); 849 mDatabase.prune(currentTimeMillis); 850 loadActiveStats(currentTimeMillis); 851 852 final int continueCount = continuePkgs.size(); 853 for (int i = 0; i < continueCount; i++) { 854 String pkgName = continuePkgs.valueAt(i); 855 final long beginTime = mCurrentStats[INTERVAL_DAILY].beginTime; 856 for (IntervalStats stat : mCurrentStats) { 857 if (continueActivity.containsKey(pkgName)) { 858 final SparseIntArray eventMap = 859 continueActivity.get(pkgName); 860 final int size = eventMap.size(); 861 for (int j = 0; j < size; j++) { 862 stat.update(pkgName, null, beginTime, 863 eventMap.valueAt(j), eventMap.keyAt(j)); 864 } 865 } 866 if (continueForegroundService.containsKey(pkgName)) { 867 final ArrayMap<String, Integer> eventMap = 868 continueForegroundService.get(pkgName); 869 final int size = eventMap.size(); 870 for (int j = 0; j < size; j++) { 871 stat.update(pkgName, eventMap.keyAt(j), beginTime, 872 eventMap.valueAt(j), 0); 873 } 874 } 875 stat.updateConfigurationStats(previousConfig, beginTime); 876 notifyStatsChanged(); 877 } 878 } 879 persistActiveStats(); 880 881 final long totalTime = SystemClock.elapsedRealtime() - startTime; 882 Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime 883 + " milliseconds"); 884 } 885 notifyStatsChanged()886 private void notifyStatsChanged() { 887 if (!mStatsChanged) { 888 mStatsChanged = true; 889 mListener.onStatsUpdated(); 890 } 891 } 892 notifyNewUpdate()893 private void notifyNewUpdate() { 894 mListener.onNewUpdate(mUserId); 895 } 896 loadActiveStats(final long currentTimeMillis)897 private void loadActiveStats(final long currentTimeMillis) { 898 for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) { 899 final IntervalStats stats = mDatabase.getLatestUsageStats(intervalType); 900 if (stats != null 901 && currentTimeMillis < stats.beginTime + INTERVAL_LENGTH[intervalType]) { 902 if (DEBUG) { 903 Slog.d(TAG, mLogPrefix + "Loading existing stats @ " + 904 sDateFormat.format(stats.beginTime) + "(" + stats.beginTime + 905 ") for interval " + intervalType); 906 } 907 mCurrentStats[intervalType] = stats; 908 } else { 909 // No good fit remains. 910 if (DEBUG) { 911 Slog.d(TAG, "Creating new stats @ " + 912 sDateFormat.format(currentTimeMillis) + "(" + 913 currentTimeMillis + ") for interval " + intervalType); 914 } 915 916 mCurrentStats[intervalType] = new IntervalStats(); 917 mCurrentStats[intervalType].beginTime = currentTimeMillis; 918 mCurrentStats[intervalType].endTime = currentTimeMillis + 1; 919 } 920 } 921 922 mStatsChanged = false; 923 updateRolloverDeadline(); 924 925 // Tell the listener that the stats reloaded, which may have changed idle states. 926 mListener.onStatsReloaded(); 927 } 928 updateRolloverDeadline()929 private void updateRolloverDeadline() { 930 mDailyExpiryDate.setTimeInMillis( 931 mCurrentStats[INTERVAL_DAILY].beginTime); 932 mDailyExpiryDate.addDays(1); 933 Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " + 934 sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" + 935 mDailyExpiryDate.getTimeInMillis() + ")"); 936 } 937 938 // 939 // -- DUMP related methods -- 940 // 941 checkin(final IndentingPrintWriter pw)942 void checkin(final IndentingPrintWriter pw) { 943 mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() { 944 @Override 945 public boolean checkin(IntervalStats stats) { 946 printIntervalStats(pw, stats, false, false, null); 947 return true; 948 } 949 }); 950 } 951 dump(IndentingPrintWriter pw, List<String> pkgs)952 void dump(IndentingPrintWriter pw, List<String> pkgs) { 953 dump(pw, pkgs, false); 954 } 955 dump(IndentingPrintWriter pw, List<String> pkgs, boolean compact)956 void dump(IndentingPrintWriter pw, List<String> pkgs, boolean compact) { 957 printLast24HrEvents(pw, !compact, pkgs); 958 for (int interval = 0; interval < mCurrentStats.length; interval++) { 959 pw.print("In-memory "); 960 pw.print(intervalToString(interval)); 961 pw.println(" stats"); 962 printIntervalStats(pw, mCurrentStats[interval], !compact, true, pkgs); 963 } 964 if (CollectionUtils.isEmpty(pkgs)) { 965 mDatabase.dump(pw, compact); 966 } 967 } 968 dumpDatabaseInfo(IndentingPrintWriter ipw)969 void dumpDatabaseInfo(IndentingPrintWriter ipw) { 970 mDatabase.dump(ipw, false); 971 } 972 dumpMappings(IndentingPrintWriter ipw)973 void dumpMappings(IndentingPrintWriter ipw) { 974 mDatabase.dumpMappings(ipw); 975 } 976 deleteDataFor(String pkg)977 void deleteDataFor(String pkg) { 978 mDatabase.deleteDataFor(pkg); 979 } 980 dumpFile(IndentingPrintWriter ipw, String[] args)981 void dumpFile(IndentingPrintWriter ipw, String[] args) { 982 if (args == null || args.length == 0) { 983 // dump all files for every interval for specified user 984 final int numIntervals = mDatabase.mSortedStatFiles.length; 985 for (int interval = 0; interval < numIntervals; interval++) { 986 ipw.println("interval=" + intervalToString(interval)); 987 ipw.increaseIndent(); 988 dumpFileDetailsForInterval(ipw, interval); 989 ipw.decreaseIndent(); 990 } 991 } else { 992 final int interval; 993 try { 994 final int intervalValue = stringToInterval(args[0]); 995 if (intervalValue == -1) { 996 interval = Integer.valueOf(args[0]); 997 } else { 998 interval = intervalValue; 999 } 1000 } catch (NumberFormatException nfe) { 1001 ipw.println("invalid interval specified."); 1002 return; 1003 } 1004 if (interval < 0 || interval >= mDatabase.mSortedStatFiles.length) { 1005 ipw.println("the specified interval does not exist."); 1006 return; 1007 } 1008 if (args.length == 1) { 1009 // dump all files in the specified interval 1010 dumpFileDetailsForInterval(ipw, interval); 1011 } else { 1012 // dump details only for the specified filename 1013 final long filename; 1014 try { 1015 filename = Long.valueOf(args[1]); 1016 } catch (NumberFormatException nfe) { 1017 ipw.println("invalid filename specified."); 1018 return; 1019 } 1020 final IntervalStats stats = mDatabase.readIntervalStatsForFile(interval, filename); 1021 if (stats == null) { 1022 ipw.println("the specified filename does not exist."); 1023 return; 1024 } 1025 dumpFileDetails(ipw, stats, Long.valueOf(args[1])); 1026 } 1027 } 1028 } 1029 dumpFileDetailsForInterval(IndentingPrintWriter ipw, int interval)1030 private void dumpFileDetailsForInterval(IndentingPrintWriter ipw, int interval) { 1031 final TimeSparseArray<AtomicFile> files = mDatabase.mSortedStatFiles[interval]; 1032 final int numFiles = files.size(); 1033 for (int i = 0; i < numFiles; i++) { 1034 final long filename = files.keyAt(i); 1035 final IntervalStats stats = mDatabase.readIntervalStatsForFile(interval, filename); 1036 dumpFileDetails(ipw, stats, filename); 1037 ipw.println(); 1038 } 1039 } 1040 dumpFileDetails(IndentingPrintWriter ipw, IntervalStats stats, long filename)1041 private void dumpFileDetails(IndentingPrintWriter ipw, IntervalStats stats, long filename) { 1042 ipw.println("file=" + filename); 1043 ipw.increaseIndent(); 1044 printIntervalStats(ipw, stats, false, false, null); 1045 ipw.decreaseIndent(); 1046 } 1047 formatDateTime(long dateTime, boolean pretty)1048 static String formatDateTime(long dateTime, boolean pretty) { 1049 if (pretty) { 1050 return "\"" + sDateFormat.format(dateTime)+ "\""; 1051 } 1052 return Long.toString(dateTime); 1053 } 1054 formatElapsedTime(long elapsedTime, boolean pretty)1055 private String formatElapsedTime(long elapsedTime, boolean pretty) { 1056 if (pretty) { 1057 return "\"" + DateUtils.formatElapsedTime(elapsedTime / 1000) + "\""; 1058 } 1059 return Long.toString(elapsedTime); 1060 } 1061 printEvent(IndentingPrintWriter pw, Event event, boolean prettyDates)1062 void printEvent(IndentingPrintWriter pw, Event event, boolean prettyDates) { 1063 pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates)); 1064 pw.printPair("type", eventToString(event.mEventType)); 1065 pw.printPair("package", event.mPackage); 1066 if (event.mClass != null) { 1067 pw.printPair("class", event.mClass); 1068 } 1069 if (event.mConfiguration != null) { 1070 pw.printPair("config", Configuration.resourceQualifierString(event.mConfiguration)); 1071 } 1072 if (event.mShortcutId != null) { 1073 pw.printPair("shortcutId", event.mShortcutId); 1074 } 1075 if (event.mEventType == Event.STANDBY_BUCKET_CHANGED) { 1076 pw.printPair("standbyBucket", event.getAppStandbyBucket()); 1077 pw.printPair("reason", UsageStatsManager.reasonToString(event.getStandbyReason())); 1078 } else if (event.mEventType == Event.ACTIVITY_RESUMED 1079 || event.mEventType == Event.ACTIVITY_PAUSED 1080 || event.mEventType == Event.ACTIVITY_STOPPED) { 1081 pw.printPair("instanceId", event.getInstanceId()); 1082 } 1083 1084 if (event.getTaskRootPackageName() != null) { 1085 pw.printPair("taskRootPackage", event.getTaskRootPackageName()); 1086 } 1087 1088 if (event.getTaskRootClassName() != null) { 1089 pw.printPair("taskRootClass", event.getTaskRootClassName()); 1090 } 1091 1092 if (event.mNotificationChannelId != null) { 1093 pw.printPair("channelId", event.mNotificationChannelId); 1094 } 1095 pw.printHexPair("flags", event.mFlags); 1096 pw.println(); 1097 } 1098 printLast24HrEvents(IndentingPrintWriter pw, boolean prettyDates, final List<String> pkgs)1099 void printLast24HrEvents(IndentingPrintWriter pw, boolean prettyDates, 1100 final List<String> pkgs) { 1101 final long endTime = System.currentTimeMillis(); 1102 UnixCalendar yesterday = new UnixCalendar(endTime); 1103 yesterday.addDays(-1); 1104 1105 final long beginTime = yesterday.getTimeInMillis(); 1106 1107 List<Event> events = queryStats(INTERVAL_DAILY, 1108 beginTime, endTime, new StatCombiner<Event>() { 1109 @Override 1110 public boolean combine(IntervalStats stats, boolean mutable, 1111 List<Event> accumulatedResult) { 1112 final int startIndex = stats.events.firstIndexOnOrAfter(beginTime); 1113 final int size = stats.events.size(); 1114 for (int i = startIndex; i < size; i++) { 1115 if (stats.events.get(i).mTimeStamp >= endTime) { 1116 return false; 1117 } 1118 1119 Event event = stats.events.get(i); 1120 if (!CollectionUtils.isEmpty(pkgs) && !pkgs.contains(event.mPackage)) { 1121 continue; 1122 } 1123 accumulatedResult.add(event); 1124 } 1125 return true; 1126 } 1127 }); 1128 1129 pw.print("Last 24 hour events ("); 1130 if (prettyDates) { 1131 pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext, 1132 beginTime, endTime, sDateFormatFlags) + "\""); 1133 } else { 1134 pw.printPair("beginTime", beginTime); 1135 pw.printPair("endTime", endTime); 1136 } 1137 pw.println(")"); 1138 if (events != null) { 1139 pw.increaseIndent(); 1140 for (Event event : events) { 1141 printEvent(pw, event, prettyDates); 1142 } 1143 pw.decreaseIndent(); 1144 } 1145 } 1146 printEventAggregation(IndentingPrintWriter pw, String label, IntervalStats.EventTracker tracker, boolean prettyDates)1147 void printEventAggregation(IndentingPrintWriter pw, String label, 1148 IntervalStats.EventTracker tracker, boolean prettyDates) { 1149 if (tracker.count != 0 || tracker.duration != 0) { 1150 pw.print(label); 1151 pw.print(": "); 1152 pw.print(tracker.count); 1153 pw.print("x for "); 1154 pw.print(formatElapsedTime(tracker.duration, prettyDates)); 1155 if (tracker.curStartTime != 0) { 1156 pw.print(" (now running, started at "); 1157 formatDateTime(tracker.curStartTime, prettyDates); 1158 pw.print(")"); 1159 } 1160 pw.println(); 1161 } 1162 } 1163 printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, boolean prettyDates, boolean skipEvents, List<String> pkgs)1164 void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, 1165 boolean prettyDates, boolean skipEvents, List<String> pkgs) { 1166 if (prettyDates) { 1167 pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext, 1168 stats.beginTime, stats.endTime, sDateFormatFlags) + "\""); 1169 } else { 1170 pw.printPair("beginTime", stats.beginTime); 1171 pw.printPair("endTime", stats.endTime); 1172 } 1173 pw.println(); 1174 pw.increaseIndent(); 1175 pw.println("packages"); 1176 pw.increaseIndent(); 1177 final ArrayMap<String, UsageStats> pkgStats = stats.packageStats; 1178 final int pkgCount = pkgStats.size(); 1179 for (int i = 0; i < pkgCount; i++) { 1180 final UsageStats usageStats = pkgStats.valueAt(i); 1181 if (!CollectionUtils.isEmpty(pkgs) && !pkgs.contains(usageStats.mPackageName)) { 1182 continue; 1183 } 1184 pw.printPair("package", usageStats.mPackageName); 1185 pw.printPair("totalTimeUsed", 1186 formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates)); 1187 pw.printPair("lastTimeUsed", formatDateTime(usageStats.mLastTimeUsed, prettyDates)); 1188 pw.printPair("totalTimeVisible", 1189 formatElapsedTime(usageStats.mTotalTimeVisible, prettyDates)); 1190 pw.printPair("lastTimeVisible", 1191 formatDateTime(usageStats.mLastTimeVisible, prettyDates)); 1192 pw.printPair("lastTimeComponentUsed", 1193 formatDateTime(usageStats.mLastTimeComponentUsed, prettyDates)); 1194 pw.printPair("totalTimeFS", 1195 formatElapsedTime(usageStats.mTotalTimeForegroundServiceUsed, prettyDates)); 1196 pw.printPair("lastTimeFS", 1197 formatDateTime(usageStats.mLastTimeForegroundServiceUsed, prettyDates)); 1198 pw.printPair("appLaunchCount", usageStats.mAppLaunchCount); 1199 pw.println(); 1200 } 1201 pw.decreaseIndent(); 1202 1203 pw.println(); 1204 pw.println("ChooserCounts"); 1205 pw.increaseIndent(); 1206 for (UsageStats usageStats : pkgStats.values()) { 1207 if (!CollectionUtils.isEmpty(pkgs) && !pkgs.contains(usageStats.mPackageName)) { 1208 continue; 1209 } 1210 pw.printPair("package", usageStats.mPackageName); 1211 if (usageStats.mChooserCounts != null) { 1212 final int chooserCountSize = usageStats.mChooserCounts.size(); 1213 for (int i = 0; i < chooserCountSize; i++) { 1214 final String action = usageStats.mChooserCounts.keyAt(i); 1215 final ArrayMap<String, Integer> counts = usageStats.mChooserCounts.valueAt(i); 1216 final int annotationSize = counts.size(); 1217 for (int j = 0; j < annotationSize; j++) { 1218 final String key = counts.keyAt(j); 1219 final int count = counts.valueAt(j); 1220 if (count != 0) { 1221 pw.printPair("ChooserCounts", action + ":" + key + " is " + 1222 Integer.toString(count)); 1223 pw.println(); 1224 } 1225 } 1226 } 1227 } 1228 pw.println(); 1229 } 1230 pw.decreaseIndent(); 1231 1232 if (CollectionUtils.isEmpty(pkgs)) { 1233 pw.println("configurations"); 1234 pw.increaseIndent(); 1235 final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations; 1236 final int configCount = configStats.size(); 1237 for (int i = 0; i < configCount; i++) { 1238 final ConfigurationStats config = configStats.valueAt(i); 1239 pw.printPair("config", Configuration.resourceQualifierString( 1240 config.mConfiguration)); 1241 pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates)); 1242 pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates)); 1243 pw.printPair("count", config.mActivationCount); 1244 pw.println(); 1245 } 1246 pw.decreaseIndent(); 1247 pw.println("event aggregations"); 1248 pw.increaseIndent(); 1249 printEventAggregation(pw, "screen-interactive", stats.interactiveTracker, 1250 prettyDates); 1251 printEventAggregation(pw, "screen-non-interactive", stats.nonInteractiveTracker, 1252 prettyDates); 1253 printEventAggregation(pw, "keyguard-shown", stats.keyguardShownTracker, 1254 prettyDates); 1255 printEventAggregation(pw, "keyguard-hidden", stats.keyguardHiddenTracker, 1256 prettyDates); 1257 pw.decreaseIndent(); 1258 } 1259 1260 // The last 24 hours of events is already printed in the non checkin dump 1261 // No need to repeat here. 1262 if (!skipEvents) { 1263 pw.println("events"); 1264 pw.increaseIndent(); 1265 final EventList events = stats.events; 1266 final int eventCount = events != null ? events.size() : 0; 1267 for (int i = 0; i < eventCount; i++) { 1268 final Event event = events.get(i); 1269 if (!CollectionUtils.isEmpty(pkgs) && !pkgs.contains(event.mPackage)) { 1270 continue; 1271 } 1272 printEvent(pw, event, prettyDates); 1273 } 1274 pw.decreaseIndent(); 1275 } 1276 pw.decreaseIndent(); 1277 } 1278 intervalToString(int interval)1279 public static String intervalToString(int interval) { 1280 switch (interval) { 1281 case INTERVAL_DAILY: 1282 return "daily"; 1283 case INTERVAL_WEEKLY: 1284 return "weekly"; 1285 case INTERVAL_MONTHLY: 1286 return "monthly"; 1287 case INTERVAL_YEARLY: 1288 return "yearly"; 1289 default: 1290 return "?"; 1291 } 1292 } 1293 stringToInterval(String interval)1294 private static int stringToInterval(String interval) { 1295 switch (interval.toLowerCase()) { 1296 case "daily": 1297 return INTERVAL_DAILY; 1298 case "weekly": 1299 return INTERVAL_WEEKLY; 1300 case "monthly": 1301 return INTERVAL_MONTHLY; 1302 case "yearly": 1303 return INTERVAL_YEARLY; 1304 default: 1305 return -1; 1306 } 1307 } 1308 eventToString(int eventType)1309 private static String eventToString(int eventType) { 1310 switch (eventType) { 1311 case Event.NONE: 1312 return "NONE"; 1313 case Event.ACTIVITY_PAUSED: 1314 return "ACTIVITY_PAUSED"; 1315 case Event.ACTIVITY_RESUMED: 1316 return "ACTIVITY_RESUMED"; 1317 case Event.FOREGROUND_SERVICE_START: 1318 return "FOREGROUND_SERVICE_START"; 1319 case Event.FOREGROUND_SERVICE_STOP: 1320 return "FOREGROUND_SERVICE_STOP"; 1321 case Event.ACTIVITY_STOPPED: 1322 return "ACTIVITY_STOPPED"; 1323 case Event.END_OF_DAY: 1324 return "END_OF_DAY"; 1325 case Event.ROLLOVER_FOREGROUND_SERVICE: 1326 return "ROLLOVER_FOREGROUND_SERVICE"; 1327 case Event.CONTINUE_PREVIOUS_DAY: 1328 return "CONTINUE_PREVIOUS_DAY"; 1329 case Event.CONTINUING_FOREGROUND_SERVICE: 1330 return "CONTINUING_FOREGROUND_SERVICE"; 1331 case Event.CONFIGURATION_CHANGE: 1332 return "CONFIGURATION_CHANGE"; 1333 case Event.SYSTEM_INTERACTION: 1334 return "SYSTEM_INTERACTION"; 1335 case Event.USER_INTERACTION: 1336 return "USER_INTERACTION"; 1337 case Event.SHORTCUT_INVOCATION: 1338 return "SHORTCUT_INVOCATION"; 1339 case Event.CHOOSER_ACTION: 1340 return "CHOOSER_ACTION"; 1341 case Event.NOTIFICATION_SEEN: 1342 return "NOTIFICATION_SEEN"; 1343 case Event.STANDBY_BUCKET_CHANGED: 1344 return "STANDBY_BUCKET_CHANGED"; 1345 case Event.NOTIFICATION_INTERRUPTION: 1346 return "NOTIFICATION_INTERRUPTION"; 1347 case Event.SLICE_PINNED: 1348 return "SLICE_PINNED"; 1349 case Event.SLICE_PINNED_PRIV: 1350 return "SLICE_PINNED_PRIV"; 1351 case Event.SCREEN_INTERACTIVE: 1352 return "SCREEN_INTERACTIVE"; 1353 case Event.SCREEN_NON_INTERACTIVE: 1354 return "SCREEN_NON_INTERACTIVE"; 1355 case Event.KEYGUARD_SHOWN: 1356 return "KEYGUARD_SHOWN"; 1357 case Event.KEYGUARD_HIDDEN: 1358 return "KEYGUARD_HIDDEN"; 1359 case Event.DEVICE_SHUTDOWN: 1360 return "DEVICE_SHUTDOWN"; 1361 case Event.DEVICE_STARTUP: 1362 return "DEVICE_STARTUP"; 1363 case Event.USER_UNLOCKED: 1364 return "USER_UNLOCKED"; 1365 case Event.USER_STOPPED: 1366 return "USER_STOPPED"; 1367 case Event.LOCUS_ID_SET: 1368 return "LOCUS_ID_SET"; 1369 case Event.APP_COMPONENT_USED: 1370 return "APP_COMPONENT_USED"; 1371 default: 1372 return "UNKNOWN_TYPE_" + eventType; 1373 } 1374 } 1375 getBackupPayload(String key)1376 byte[] getBackupPayload(String key){ 1377 checkAndGetTimeLocked(); 1378 persistActiveStats(); 1379 return mDatabase.getBackupPayload(key); 1380 } 1381 applyRestoredPayload(String key, byte[] payload)1382 Set<String> applyRestoredPayload(String key, byte[] payload) { 1383 checkAndGetTimeLocked(); 1384 return mDatabase.applyRestoredPayload(key, payload); 1385 } 1386 } 1387