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