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