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