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