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