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 package com.android.server.usage; 17 18 import static android.app.usage.UsageEvents.Event.ACTIVITY_PAUSED; 19 import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED; 20 import static android.app.usage.UsageEvents.Event.ACTIVITY_STOPPED; 21 import static android.app.usage.UsageEvents.Event.CONFIGURATION_CHANGE; 22 import static android.app.usage.UsageEvents.Event.CONTINUE_PREVIOUS_DAY; 23 import static android.app.usage.UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE; 24 import static android.app.usage.UsageEvents.Event.DEVICE_SHUTDOWN; 25 import static android.app.usage.UsageEvents.Event.END_OF_DAY; 26 import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK; 27 import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START; 28 import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_STOP; 29 import static android.app.usage.UsageEvents.Event.KEYGUARD_HIDDEN; 30 import static android.app.usage.UsageEvents.Event.KEYGUARD_SHOWN; 31 import static android.app.usage.UsageEvents.Event.LOCUS_ID_SET; 32 import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION; 33 import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE; 34 import static android.app.usage.UsageEvents.Event.SCREEN_INTERACTIVE; 35 import static android.app.usage.UsageEvents.Event.SCREEN_NON_INTERACTIVE; 36 import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION; 37 import static android.app.usage.UsageEvents.Event.STANDBY_BUCKET_CHANGED; 38 import static android.app.usage.UsageEvents.Event.SYSTEM_INTERACTION; 39 40 import android.app.usage.ConfigurationStats; 41 import android.app.usage.EventList; 42 import android.app.usage.EventStats; 43 import android.app.usage.UsageEvents.Event; 44 import android.app.usage.UsageStats; 45 import android.content.res.Configuration; 46 import android.text.TextUtils; 47 import android.util.ArrayMap; 48 import android.util.ArraySet; 49 import android.util.Slog; 50 import android.util.SparseArray; 51 import android.util.SparseIntArray; 52 import android.util.proto.ProtoInputStream; 53 54 import com.android.internal.annotations.VisibleForTesting; 55 56 import java.io.IOException; 57 import java.util.Arrays; 58 import java.util.List; 59 60 public class IntervalStats { 61 private static final String TAG = "IntervalStats"; 62 63 public static final int CURRENT_MAJOR_VERSION = 1; 64 public static final int CURRENT_MINOR_VERSION = 1; 65 public int majorVersion = CURRENT_MAJOR_VERSION; 66 public int minorVersion = CURRENT_MINOR_VERSION; 67 public long beginTime; 68 public long endTime; 69 public long lastTimeSaved; 70 public final EventTracker interactiveTracker = new EventTracker(); 71 public final EventTracker nonInteractiveTracker = new EventTracker(); 72 public final EventTracker keyguardShownTracker = new EventTracker(); 73 public final EventTracker keyguardHiddenTracker = new EventTracker(); 74 public final ArrayMap<String, UsageStats> packageStats = new ArrayMap<>(); 75 /** @hide */ 76 public final SparseArray<UsageStats> packageStatsObfuscated = new SparseArray<>(); 77 public final ArrayMap<Configuration, ConfigurationStats> configurations = new ArrayMap<>(); 78 public Configuration activeConfiguration; 79 public final EventList events = new EventList(); 80 81 // A string cache. This is important as when we're parsing XML files, we don't want to 82 // keep hundreds of strings that have the same contents. We will read the string 83 // and only keep it if it's not in the cache. The GC will take care of the 84 // strings that had identical copies in the cache. 85 public final ArraySet<String> mStringCache = new ArraySet<>(); 86 87 public static final class EventTracker { 88 public long curStartTime; 89 public long lastEventTime; 90 public long duration; 91 public int count; 92 commitTime(long timeStamp)93 public void commitTime(long timeStamp) { 94 if (curStartTime != 0) { 95 duration += timeStamp - curStartTime; 96 curStartTime = 0; 97 } 98 } 99 update(long timeStamp)100 public void update(long timeStamp) { 101 if (curStartTime == 0) { 102 // If we aren't already running, time to bump the count. 103 count++; 104 } 105 commitTime(timeStamp); 106 curStartTime = timeStamp; 107 lastEventTime = timeStamp; 108 } 109 addToEventStats(List<EventStats> out, int event, long beginTime, long endTime)110 void addToEventStats(List<EventStats> out, int event, long beginTime, long endTime) { 111 if (count != 0 || duration != 0) { 112 EventStats ev = new EventStats(); 113 ev.mEventType = event; 114 ev.mCount = count; 115 ev.mTotalTime = duration; 116 ev.mLastEventTime = lastEventTime; 117 ev.mBeginTimeStamp = beginTime; 118 ev.mEndTimeStamp = endTime; 119 out.add(ev); 120 } 121 } 122 123 } 124 IntervalStats()125 public IntervalStats() { 126 } 127 128 /** 129 * Gets the UsageStats object for the given package, or creates one and adds it internally. 130 */ getOrCreateUsageStats(String packageName)131 UsageStats getOrCreateUsageStats(String packageName) { 132 UsageStats usageStats = packageStats.get(packageName); 133 if (usageStats == null) { 134 usageStats = new UsageStats(); 135 usageStats.mPackageName = getCachedStringRef(packageName); 136 usageStats.mBeginTimeStamp = beginTime; 137 usageStats.mEndTimeStamp = endTime; 138 packageStats.put(usageStats.mPackageName, usageStats); 139 } 140 return usageStats; 141 } 142 143 /** 144 * Gets the ConfigurationStats object for the given configuration, or creates one and adds it 145 * internally. 146 */ getOrCreateConfigurationStats(Configuration config)147 ConfigurationStats getOrCreateConfigurationStats(Configuration config) { 148 ConfigurationStats configStats = configurations.get(config); 149 if (configStats == null) { 150 configStats = new ConfigurationStats(); 151 configStats.mBeginTimeStamp = beginTime; 152 configStats.mEndTimeStamp = endTime; 153 configStats.mConfiguration = config; 154 configurations.put(config, configStats); 155 } 156 return configStats; 157 } 158 159 /** 160 * Builds a UsageEvents.Event, but does not add it internally. 161 */ buildEvent(String packageName, String className)162 Event buildEvent(String packageName, String className) { 163 Event event = new Event(); 164 event.mPackage = getCachedStringRef(packageName); 165 if (className != null) { 166 event.mClass = getCachedStringRef(className); 167 } 168 return event; 169 } 170 171 /** 172 * Builds a UsageEvents.Event from a proto, but does not add it internally. 173 * Built here to take advantage of the cached String Refs 174 */ buildEvent(ProtoInputStream parser, List<String> stringPool)175 Event buildEvent(ProtoInputStream parser, List<String> stringPool) 176 throws IOException { 177 final Event event = new Event(); 178 while (true) { 179 switch (parser.nextField()) { 180 case (int) IntervalStatsProto.Event.PACKAGE: 181 event.mPackage = getCachedStringRef( 182 parser.readString(IntervalStatsProto.Event.PACKAGE)); 183 break; 184 case (int) IntervalStatsProto.Event.PACKAGE_INDEX: 185 event.mPackage = getCachedStringRef(stringPool.get( 186 parser.readInt(IntervalStatsProto.Event.PACKAGE_INDEX) - 1)); 187 break; 188 case (int) IntervalStatsProto.Event.CLASS: 189 event.mClass = getCachedStringRef( 190 parser.readString(IntervalStatsProto.Event.CLASS)); 191 break; 192 case (int) IntervalStatsProto.Event.CLASS_INDEX: 193 event.mClass = getCachedStringRef(stringPool.get( 194 parser.readInt(IntervalStatsProto.Event.CLASS_INDEX) - 1)); 195 break; 196 case (int) IntervalStatsProto.Event.TIME_MS: 197 event.mTimeStamp = beginTime + parser.readLong( 198 IntervalStatsProto.Event.TIME_MS); 199 break; 200 case (int) IntervalStatsProto.Event.FLAGS: 201 event.mFlags = parser.readInt(IntervalStatsProto.Event.FLAGS); 202 break; 203 case (int) IntervalStatsProto.Event.TYPE: 204 event.mEventType = parser.readInt(IntervalStatsProto.Event.TYPE); 205 break; 206 case (int) IntervalStatsProto.Event.CONFIG: 207 event.mConfiguration = new Configuration(); 208 event.mConfiguration.readFromProto(parser, IntervalStatsProto.Event.CONFIG); 209 break; 210 case (int) IntervalStatsProto.Event.SHORTCUT_ID: 211 event.mShortcutId = parser.readString( 212 IntervalStatsProto.Event.SHORTCUT_ID).intern(); 213 break; 214 case (int) IntervalStatsProto.Event.STANDBY_BUCKET: 215 event.mBucketAndReason = parser.readInt( 216 IntervalStatsProto.Event.STANDBY_BUCKET); 217 break; 218 case (int) IntervalStatsProto.Event.NOTIFICATION_CHANNEL: 219 event.mNotificationChannelId = parser.readString( 220 IntervalStatsProto.Event.NOTIFICATION_CHANNEL); 221 break; 222 case (int) IntervalStatsProto.Event.NOTIFICATION_CHANNEL_INDEX: 223 event.mNotificationChannelId = getCachedStringRef(stringPool.get( 224 parser.readInt(IntervalStatsProto.Event.NOTIFICATION_CHANNEL_INDEX) 225 - 1)); 226 break; 227 case (int) IntervalStatsProto.Event.INSTANCE_ID: 228 event.mInstanceId = parser.readInt(IntervalStatsProto.Event.INSTANCE_ID); 229 break; 230 case (int) IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX: 231 event.mTaskRootPackage = getCachedStringRef(stringPool.get( 232 parser.readInt(IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX) - 1)); 233 break; 234 case (int) IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX: 235 event.mTaskRootClass = getCachedStringRef(stringPool.get( 236 parser.readInt(IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX) - 1)); 237 break; 238 case (int) IntervalStatsProto.Event.LOCUS_ID_INDEX: 239 event.mLocusId = getCachedStringRef(stringPool.get( 240 parser.readInt(IntervalStatsProto.Event.LOCUS_ID_INDEX) - 1)); 241 break; 242 case ProtoInputStream.NO_MORE_FIELDS: 243 // Handle default values for certain events types 244 switch (event.mEventType) { 245 case CONFIGURATION_CHANGE: 246 if (event.mConfiguration == null) { 247 event.mConfiguration = new Configuration(); 248 } 249 break; 250 case SHORTCUT_INVOCATION: 251 if (event.mShortcutId == null) { 252 event.mShortcutId = ""; 253 } 254 break; 255 case NOTIFICATION_INTERRUPTION: 256 if (event.mNotificationChannelId == null) { 257 event.mNotificationChannelId = ""; 258 } 259 break; 260 case LOCUS_ID_SET: 261 if (event.mLocusId == null) { 262 event.mLocusId = ""; 263 } 264 break; 265 } 266 return event; 267 } 268 } 269 } 270 isStatefulEvent(int eventType)271 private boolean isStatefulEvent(int eventType) { 272 switch (eventType) { 273 case ACTIVITY_RESUMED: 274 case ACTIVITY_PAUSED: 275 case ACTIVITY_STOPPED: 276 case FOREGROUND_SERVICE_START: 277 case FOREGROUND_SERVICE_STOP: 278 case END_OF_DAY: 279 case ROLLOVER_FOREGROUND_SERVICE: 280 case CONTINUE_PREVIOUS_DAY: 281 case CONTINUING_FOREGROUND_SERVICE: 282 case DEVICE_SHUTDOWN: 283 return true; 284 } 285 return false; 286 } 287 288 /** 289 * Returns whether the event type is one caused by user visible 290 * interaction. Excludes those that are internally generated. 291 */ isUserVisibleEvent(int eventType)292 private boolean isUserVisibleEvent(int eventType) { 293 return eventType != SYSTEM_INTERACTION 294 && eventType != STANDBY_BUCKET_CHANGED; 295 } 296 297 /** 298 * Update the IntervalStats by a activity or foreground service event. 299 * @param packageName package name of this event. Is null if event targets to all packages. 300 * @param className class name of a activity or foreground service, could be null to if this 301 * is sent to all activities/services in this package. 302 * @param timeStamp Epoch timestamp in milliseconds. 303 * @param eventType event type as in {@link Event} 304 * @param instanceId if className is an activity, the hashCode of ActivityRecord's appToken. 305 * if className is not an activity, instanceId is not used. 306 * @hide 307 */ 308 @VisibleForTesting update(String packageName, String className, long timeStamp, int eventType, int instanceId)309 public void update(String packageName, String className, long timeStamp, int eventType, 310 int instanceId) { 311 if (eventType == DEVICE_SHUTDOWN 312 || eventType == FLUSH_TO_DISK) { 313 // DEVICE_SHUTDOWN and FLUSH_TO_DISK are sent to all packages. 314 final int size = packageStats.size(); 315 for (int i = 0; i < size; i++) { 316 UsageStats usageStats = packageStats.valueAt(i); 317 usageStats.update(null, timeStamp, eventType, instanceId); 318 } 319 } else { 320 UsageStats usageStats = getOrCreateUsageStats(packageName); 321 usageStats.update(className, timeStamp, eventType, instanceId); 322 } 323 if (timeStamp > endTime) { 324 endTime = timeStamp; 325 } 326 } 327 328 /** 329 * @hide 330 */ 331 @VisibleForTesting addEvent(Event event)332 public void addEvent(Event event) { 333 // Cache common use strings 334 event.mPackage = getCachedStringRef(event.mPackage); 335 if (event.mClass != null) { 336 event.mClass = getCachedStringRef(event.mClass); 337 } 338 if (event.mTaskRootPackage != null) { 339 event.mTaskRootPackage = getCachedStringRef(event.mTaskRootPackage); 340 } 341 if (event.mTaskRootClass != null) { 342 event.mTaskRootClass = getCachedStringRef(event.mTaskRootClass); 343 } 344 if (event.mEventType == NOTIFICATION_INTERRUPTION) { 345 event.mNotificationChannelId = getCachedStringRef(event.mNotificationChannelId); 346 } 347 events.insert(event); 348 if (event.mTimeStamp > endTime) { 349 endTime = event.mTimeStamp; 350 } 351 } 352 updateChooserCounts(String packageName, String category, String action)353 void updateChooserCounts(String packageName, String category, String action) { 354 UsageStats usageStats = getOrCreateUsageStats(packageName); 355 if (usageStats.mChooserCounts == null) { 356 usageStats.mChooserCounts = new ArrayMap<>(); 357 } 358 ArrayMap<String, Integer> chooserCounts; 359 final int idx = usageStats.mChooserCounts.indexOfKey(action); 360 if (idx < 0) { 361 chooserCounts = new ArrayMap<>(); 362 usageStats.mChooserCounts.put(action, chooserCounts); 363 } else { 364 chooserCounts = usageStats.mChooserCounts.valueAt(idx); 365 } 366 int currentCount = chooserCounts.getOrDefault(category, 0); 367 chooserCounts.put(category, currentCount + 1); 368 } 369 updateConfigurationStats(Configuration config, long timeStamp)370 void updateConfigurationStats(Configuration config, long timeStamp) { 371 if (activeConfiguration != null) { 372 ConfigurationStats activeStats = configurations.get(activeConfiguration); 373 activeStats.mTotalTimeActive += timeStamp - activeStats.mLastTimeActive; 374 activeStats.mLastTimeActive = timeStamp - 1; 375 } 376 377 if (config != null) { 378 ConfigurationStats configStats = getOrCreateConfigurationStats(config); 379 configStats.mLastTimeActive = timeStamp; 380 configStats.mActivationCount += 1; 381 activeConfiguration = configStats.mConfiguration; 382 } 383 if (timeStamp > endTime) { 384 endTime = timeStamp; 385 } 386 } 387 incrementAppLaunchCount(String packageName)388 void incrementAppLaunchCount(String packageName) { 389 UsageStats usageStats = getOrCreateUsageStats(packageName); 390 usageStats.mAppLaunchCount += 1; 391 } 392 commitTime(long timeStamp)393 void commitTime(long timeStamp) { 394 interactiveTracker.commitTime(timeStamp); 395 nonInteractiveTracker.commitTime(timeStamp); 396 keyguardShownTracker.commitTime(timeStamp); 397 keyguardHiddenTracker.commitTime(timeStamp); 398 } 399 updateScreenInteractive(long timeStamp)400 void updateScreenInteractive(long timeStamp) { 401 interactiveTracker.update(timeStamp); 402 nonInteractiveTracker.commitTime(timeStamp); 403 } 404 updateScreenNonInteractive(long timeStamp)405 void updateScreenNonInteractive(long timeStamp) { 406 nonInteractiveTracker.update(timeStamp); 407 interactiveTracker.commitTime(timeStamp); 408 } 409 updateKeyguardShown(long timeStamp)410 void updateKeyguardShown(long timeStamp) { 411 keyguardShownTracker.update(timeStamp); 412 keyguardHiddenTracker.commitTime(timeStamp); 413 } 414 updateKeyguardHidden(long timeStamp)415 void updateKeyguardHidden(long timeStamp) { 416 keyguardHiddenTracker.update(timeStamp); 417 keyguardShownTracker.commitTime(timeStamp); 418 } 419 addEventStatsTo(List<EventStats> out)420 void addEventStatsTo(List<EventStats> out) { 421 interactiveTracker.addToEventStats(out, SCREEN_INTERACTIVE, 422 beginTime, endTime); 423 nonInteractiveTracker.addToEventStats(out, SCREEN_NON_INTERACTIVE, 424 beginTime, endTime); 425 keyguardShownTracker.addToEventStats(out, KEYGUARD_SHOWN, 426 beginTime, endTime); 427 keyguardHiddenTracker.addToEventStats(out, KEYGUARD_HIDDEN, 428 beginTime, endTime); 429 } 430 getCachedStringRef(String str)431 private String getCachedStringRef(String str) { 432 final int index = mStringCache.indexOf(str); 433 if (index < 0) { 434 mStringCache.add(str); 435 return str; 436 } 437 return mStringCache.valueAt(index); 438 } 439 440 /** 441 * When an IntervalStats object is deserialized, if the object's version number 442 * is lower than current version number, optionally perform a upgrade. 443 */ upgradeIfNeeded()444 void upgradeIfNeeded() { 445 // We only uprade on majorVersion change, no need to upgrade on minorVersion change. 446 if (!(majorVersion < CURRENT_MAJOR_VERSION)) { 447 return; 448 } 449 /* 450 Optional upgrade code here. 451 */ 452 majorVersion = CURRENT_MAJOR_VERSION; 453 } 454 455 /** 456 * Parses all of the tokens to strings in the obfuscated usage stats data. This includes 457 * deobfuscating each of the package tokens and chooser actions and categories. 458 * 459 * @return {@code true} if any stats were omitted while deobfuscating, {@code false} otherwise. 460 */ deobfuscateUsageStats(PackagesTokenData packagesTokenData)461 private boolean deobfuscateUsageStats(PackagesTokenData packagesTokenData) { 462 boolean dataOmitted = false; 463 final ArraySet<Integer> omittedTokens = new ArraySet<>(); 464 final int usageStatsSize = packageStatsObfuscated.size(); 465 for (int statsIndex = 0; statsIndex < usageStatsSize; statsIndex++) { 466 final int packageToken = packageStatsObfuscated.keyAt(statsIndex); 467 final UsageStats usageStats = packageStatsObfuscated.valueAt(statsIndex); 468 usageStats.mPackageName = packagesTokenData.getPackageString(packageToken); 469 if (usageStats.mPackageName == null) { 470 omittedTokens.add(packageToken); 471 dataOmitted = true; 472 continue; 473 } 474 475 // Update chooser counts 476 final int chooserActionsSize = usageStats.mChooserCountsObfuscated.size(); 477 for (int actionIndex = 0; actionIndex < chooserActionsSize; actionIndex++) { 478 final ArrayMap<String, Integer> categoryCountsMap = new ArrayMap<>(); 479 final int actionToken = usageStats.mChooserCountsObfuscated.keyAt(actionIndex); 480 final String action = packagesTokenData.getString(packageToken, actionToken); 481 if (action == null) { 482 continue; 483 } 484 final SparseIntArray categoryCounts = 485 usageStats.mChooserCountsObfuscated.valueAt(actionIndex); 486 final int categoriesSize = categoryCounts.size(); 487 for (int categoryIndex = 0; categoryIndex < categoriesSize; categoryIndex++) { 488 final int categoryToken = categoryCounts.keyAt(categoryIndex); 489 final String category = packagesTokenData.getString(packageToken, 490 categoryToken); 491 if (category == null) { 492 continue; 493 } 494 categoryCountsMap.put(category, categoryCounts.valueAt(categoryIndex)); 495 } 496 usageStats.mChooserCounts.put(action, categoryCountsMap); 497 } 498 packageStats.put(usageStats.mPackageName, usageStats); 499 } 500 if (dataOmitted) { 501 Slog.d(TAG, "Unable to parse usage stats packages: " 502 + Arrays.toString(omittedTokens.toArray())); 503 } 504 return dataOmitted; 505 } 506 507 /** 508 * Parses all of the tokens to strings in the obfuscated events data. This includes 509 * deobfuscating the package token, along with any class, task root package/class tokens, and 510 * shortcut or notification channel tokens. 511 * 512 * @return {@code true} if any events were omitted while deobfuscating, {@code false} otherwise. 513 */ deobfuscateEvents(PackagesTokenData packagesTokenData)514 private boolean deobfuscateEvents(PackagesTokenData packagesTokenData) { 515 boolean dataOmitted = false; 516 final ArraySet<Integer> omittedTokens = new ArraySet<>(); 517 for (int i = this.events.size() - 1; i >= 0; i--) { 518 final Event event = this.events.get(i); 519 final int packageToken = event.mPackageToken; 520 event.mPackage = packagesTokenData.getPackageString(packageToken); 521 if (event.mPackage == null) { 522 omittedTokens.add(packageToken); 523 this.events.remove(i); 524 dataOmitted = true; 525 continue; 526 } 527 528 if (event.mClassToken != PackagesTokenData.UNASSIGNED_TOKEN) { 529 event.mClass = packagesTokenData.getString(packageToken, event.mClassToken); 530 } 531 if (event.mTaskRootPackageToken != PackagesTokenData.UNASSIGNED_TOKEN) { 532 event.mTaskRootPackage = packagesTokenData.getString(packageToken, 533 event.mTaskRootPackageToken); 534 } 535 if (event.mTaskRootClassToken != PackagesTokenData.UNASSIGNED_TOKEN) { 536 event.mTaskRootClass = packagesTokenData.getString(packageToken, 537 event.mTaskRootClassToken); 538 } 539 switch (event.mEventType) { 540 case CONFIGURATION_CHANGE: 541 if (event.mConfiguration == null) { 542 event.mConfiguration = new Configuration(); 543 } 544 break; 545 case SHORTCUT_INVOCATION: 546 event.mShortcutId = packagesTokenData.getString(packageToken, 547 event.mShortcutIdToken); 548 if (event.mShortcutId == null) { 549 Slog.v(TAG, "Unable to parse shortcut " + event.mShortcutIdToken 550 + " for package " + packageToken); 551 this.events.remove(i); 552 dataOmitted = true; 553 continue; 554 } 555 break; 556 case NOTIFICATION_INTERRUPTION: 557 event.mNotificationChannelId = packagesTokenData.getString(packageToken, 558 event.mNotificationChannelIdToken); 559 if (event.mNotificationChannelId == null) { 560 Slog.v(TAG, "Unable to parse notification channel " 561 + event.mNotificationChannelIdToken + " for package " 562 + packageToken); 563 this.events.remove(i); 564 dataOmitted = true; 565 continue; 566 } 567 break; 568 case LOCUS_ID_SET: 569 event.mLocusId = packagesTokenData.getString(packageToken, event.mLocusIdToken); 570 if (event.mLocusId == null) { 571 Slog.v(TAG, "Unable to parse locus " + event.mLocusIdToken 572 + " for package " + packageToken); 573 this.events.remove(i); 574 dataOmitted = true; 575 continue; 576 } 577 break; 578 } 579 } 580 if (dataOmitted) { 581 Slog.d(TAG, "Unable to parse event packages: " 582 + Arrays.toString(omittedTokens.toArray())); 583 } 584 return dataOmitted; 585 } 586 587 /** 588 * Parses the obfuscated tokenized data held in this interval stats object. 589 * 590 * @return {@code true} if any data was omitted while deobfuscating, {@code false} otherwise. 591 * @hide 592 */ deobfuscateData(PackagesTokenData packagesTokenData)593 public boolean deobfuscateData(PackagesTokenData packagesTokenData) { 594 final boolean statsOmitted = deobfuscateUsageStats(packagesTokenData); 595 final boolean eventsOmitted = deobfuscateEvents(packagesTokenData); 596 return statsOmitted || eventsOmitted; 597 } 598 599 /** 600 * Obfuscates certain strings within each package stats such as the package name, and the 601 * chooser actions and categories. 602 */ obfuscateUsageStatsData(PackagesTokenData packagesTokenData)603 private void obfuscateUsageStatsData(PackagesTokenData packagesTokenData) { 604 final int usageStatsSize = packageStats.size(); 605 for (int statsIndex = 0; statsIndex < usageStatsSize; statsIndex++) { 606 final String packageName = packageStats.keyAt(statsIndex); 607 final UsageStats usageStats = packageStats.valueAt(statsIndex); 608 if (usageStats == null) { 609 continue; 610 } 611 612 final int packageToken = packagesTokenData.getPackageTokenOrAdd( 613 packageName, usageStats.mEndTimeStamp); 614 // don't obfuscate stats whose packages have been removed 615 if (packageToken == PackagesTokenData.UNASSIGNED_TOKEN) { 616 continue; 617 } 618 usageStats.mPackageToken = packageToken; 619 // Update chooser counts. 620 final int chooserActionsSize = usageStats.mChooserCounts.size(); 621 for (int actionIndex = 0; actionIndex < chooserActionsSize; actionIndex++) { 622 final String action = usageStats.mChooserCounts.keyAt(actionIndex); 623 final ArrayMap<String, Integer> categoriesMap = 624 usageStats.mChooserCounts.valueAt(actionIndex); 625 if (categoriesMap == null) { 626 continue; 627 } 628 629 final SparseIntArray categoryCounts = new SparseIntArray(); 630 final int categoriesSize = categoriesMap.size(); 631 for (int categoryIndex = 0; categoryIndex < categoriesSize; categoryIndex++) { 632 String category = categoriesMap.keyAt(categoryIndex); 633 int categoryToken = packagesTokenData.getTokenOrAdd(packageToken, packageName, 634 category); 635 categoryCounts.put(categoryToken, categoriesMap.valueAt(categoryIndex)); 636 } 637 int actionToken = packagesTokenData.getTokenOrAdd(packageToken, packageName, 638 action); 639 usageStats.mChooserCountsObfuscated.put(actionToken, categoryCounts); 640 } 641 packageStatsObfuscated.put(packageToken, usageStats); 642 } 643 } 644 645 /** 646 * Obfuscates certain strings within an event such as the package name, the class name, 647 * task root package and class names, and shortcut and notification channel ids. 648 */ obfuscateEventsData(PackagesTokenData packagesTokenData)649 private void obfuscateEventsData(PackagesTokenData packagesTokenData) { 650 for (int i = events.size() - 1; i >= 0; i--) { 651 final Event event = events.get(i); 652 if (event == null) { 653 continue; 654 } 655 656 final int packageToken = packagesTokenData.getPackageTokenOrAdd( 657 event.mPackage, event.mTimeStamp); 658 // don't obfuscate events from packages that have been removed 659 if (packageToken == PackagesTokenData.UNASSIGNED_TOKEN) { 660 events.remove(i); 661 continue; 662 } 663 event.mPackageToken = packageToken; 664 if (!TextUtils.isEmpty(event.mClass)) { 665 event.mClassToken = packagesTokenData.getTokenOrAdd(packageToken, 666 event.mPackage, event.mClass); 667 } 668 if (!TextUtils.isEmpty(event.mTaskRootPackage)) { 669 event.mTaskRootPackageToken = packagesTokenData.getTokenOrAdd(packageToken, 670 event.mPackage, event.mTaskRootPackage); 671 } 672 if (!TextUtils.isEmpty(event.mTaskRootClass)) { 673 event.mTaskRootClassToken = packagesTokenData.getTokenOrAdd(packageToken, 674 event.mPackage, event.mTaskRootClass); 675 } 676 switch (event.mEventType) { 677 case SHORTCUT_INVOCATION: 678 if (!TextUtils.isEmpty(event.mShortcutId)) { 679 event.mShortcutIdToken = packagesTokenData.getTokenOrAdd(packageToken, 680 event.mPackage, event.mShortcutId); 681 } 682 break; 683 case NOTIFICATION_INTERRUPTION: 684 if (!TextUtils.isEmpty(event.mNotificationChannelId)) { 685 event.mNotificationChannelIdToken = packagesTokenData.getTokenOrAdd( 686 packageToken, event.mPackage, event.mNotificationChannelId); 687 } 688 break; 689 case LOCUS_ID_SET: 690 if (!TextUtils.isEmpty(event.mLocusId)) { 691 event.mLocusIdToken = packagesTokenData.getTokenOrAdd(packageToken, 692 event.mPackage, event.mLocusId); 693 } 694 break; 695 } 696 } 697 } 698 699 /** 700 * Obfuscates the data in this instance of interval stats. 701 * 702 * @hide 703 */ obfuscateData(PackagesTokenData packagesTokenData)704 public void obfuscateData(PackagesTokenData packagesTokenData) { 705 obfuscateUsageStatsData(packagesTokenData); 706 obfuscateEventsData(packagesTokenData); 707 } 708 } 709