1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy 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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.server.usage;
17 
18 import android.app.usage.ConfigurationStats;
19 import android.app.usage.UsageEvents;
20 import android.app.usage.UsageStats;
21 import android.content.res.Configuration;
22 import android.util.Pair;
23 import android.util.Slog;
24 import android.util.SparseArray;
25 import android.util.SparseIntArray;
26 import android.util.proto.ProtoInputStream;
27 import android.util.proto.ProtoOutputStream;
28 
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.util.ArrayList;
33 import java.util.LinkedList;
34 import java.util.Map;
35 import java.util.concurrent.TimeUnit;
36 
37 /**
38  * UsageStats reader/writer V2 for Protocol Buffer format.
39  */
40 final class UsageStatsProtoV2 {
41     private static final String TAG = "UsageStatsProtoV2";
42 
43     private static final long ONE_HOUR_MS = TimeUnit.HOURS.toMillis(1);
44 
45     // Static-only utility class.
UsageStatsProtoV2()46     private UsageStatsProtoV2() {}
47 
parseUsageStats(ProtoInputStream proto, final long beginTime)48     private static UsageStats parseUsageStats(ProtoInputStream proto, final long beginTime)
49             throws IOException {
50         UsageStats stats = new UsageStats();
51         // Time attributes stored is an offset of the beginTime.
52         while (true) {
53             switch (proto.nextField()) {
54                 case (int) UsageStatsObfuscatedProto.PACKAGE_TOKEN:
55                     stats.mPackageToken = proto.readInt(
56                             UsageStatsObfuscatedProto.PACKAGE_TOKEN) - 1;
57                     break;
58                 case (int) UsageStatsObfuscatedProto.LAST_TIME_ACTIVE_MS:
59                     stats.mLastTimeUsed = beginTime + proto.readLong(
60                             UsageStatsObfuscatedProto.LAST_TIME_ACTIVE_MS);
61                     break;
62                 case (int) UsageStatsObfuscatedProto.TOTAL_TIME_ACTIVE_MS:
63                     stats.mTotalTimeInForeground = proto.readLong(
64                             UsageStatsObfuscatedProto.TOTAL_TIME_ACTIVE_MS);
65                     break;
66                 case (int) UsageStatsObfuscatedProto.APP_LAUNCH_COUNT:
67                     stats.mAppLaunchCount = proto.readInt(
68                             UsageStatsObfuscatedProto.APP_LAUNCH_COUNT);
69                     break;
70                 case (int) UsageStatsObfuscatedProto.CHOOSER_ACTIONS:
71                     try {
72                         final long token = proto.start(UsageStatsObfuscatedProto.CHOOSER_ACTIONS);
73                         loadChooserCounts(proto, stats);
74                         proto.end(token);
75                     } catch (IOException e) {
76                         Slog.e(TAG, "Unable to read chooser counts for " + stats.mPackageToken);
77                     }
78                     break;
79                 case (int) UsageStatsObfuscatedProto.LAST_TIME_SERVICE_USED_MS:
80                     stats.mLastTimeForegroundServiceUsed = beginTime + proto.readLong(
81                             UsageStatsObfuscatedProto.LAST_TIME_SERVICE_USED_MS);
82                     break;
83                 case (int) UsageStatsObfuscatedProto.TOTAL_TIME_SERVICE_USED_MS:
84                     stats.mTotalTimeForegroundServiceUsed = proto.readLong(
85                             UsageStatsObfuscatedProto.TOTAL_TIME_SERVICE_USED_MS);
86                     break;
87                 case (int) UsageStatsObfuscatedProto.LAST_TIME_VISIBLE_MS:
88                     stats.mLastTimeVisible = beginTime + proto.readLong(
89                             UsageStatsObfuscatedProto.LAST_TIME_VISIBLE_MS);
90                     break;
91                 case (int) UsageStatsObfuscatedProto.TOTAL_TIME_VISIBLE_MS:
92                     stats.mTotalTimeVisible = proto.readLong(
93                             UsageStatsObfuscatedProto.TOTAL_TIME_VISIBLE_MS);
94                     break;
95                 case (int) UsageStatsObfuscatedProto.LAST_TIME_COMPONENT_USED_MS:
96                     stats.mLastTimeComponentUsed = beginTime + proto.readLong(
97                             UsageStatsObfuscatedProto.LAST_TIME_COMPONENT_USED_MS);
98                     break;
99                 case ProtoInputStream.NO_MORE_FIELDS:
100                     return stats;
101             }
102         }
103     }
104 
loadCountAndTime(ProtoInputStream proto, long fieldId, IntervalStats.EventTracker tracker)105     private static void loadCountAndTime(ProtoInputStream proto, long fieldId,
106             IntervalStats.EventTracker tracker) {
107         try {
108             final long token = proto.start(fieldId);
109             while (true) {
110                 switch (proto.nextField()) {
111                     case (int) IntervalStatsObfuscatedProto.CountAndTime.COUNT:
112                         tracker.count = proto.readInt(
113                                 IntervalStatsObfuscatedProto.CountAndTime.COUNT);
114                         break;
115                     case (int) IntervalStatsObfuscatedProto.CountAndTime.TIME_MS:
116                         tracker.duration = proto.readLong(
117                                 IntervalStatsObfuscatedProto.CountAndTime.TIME_MS);
118                         break;
119                     case ProtoInputStream.NO_MORE_FIELDS:
120                         proto.end(token);
121                         return;
122                 }
123             }
124         } catch (IOException e) {
125             Slog.e(TAG, "Unable to read event tracker " + fieldId, e);
126         }
127     }
128 
loadChooserCounts(ProtoInputStream proto, UsageStats usageStats)129     private static void loadChooserCounts(ProtoInputStream proto, UsageStats usageStats)
130             throws IOException {
131         int actionToken;
132         SparseIntArray counts;
133         if (proto.nextField(UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN)) {
134             // Fast path; this should work for most cases since the action token is written first
135             actionToken = proto.readInt(UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN) - 1;
136             counts = usageStats.mChooserCountsObfuscated.get(actionToken);
137             if (counts == null) {
138                 counts = new SparseIntArray();
139                 usageStats.mChooserCountsObfuscated.put(actionToken, counts);
140             }
141         } else {
142             counts = new SparseIntArray();
143         }
144 
145         while (true) {
146             switch (proto.nextField()) {
147                 case (int) UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN:
148                     // Fast path failed for some reason, add the SparseIntArray object to usageStats
149                     actionToken = proto.readInt(
150                             UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN) - 1;
151                     usageStats.mChooserCountsObfuscated.put(actionToken, counts);
152                     break;
153                 case (int) UsageStatsObfuscatedProto.ChooserAction.COUNTS:
154                     final long token = proto.start(UsageStatsObfuscatedProto.ChooserAction.COUNTS);
155                     loadCountsForAction(proto, counts);
156                     proto.end(token);
157                     break;
158                 case ProtoInputStream.NO_MORE_FIELDS:
159                     return; // if the action was never read, the loaded counts will be ignored.
160             }
161         }
162     }
163 
loadCountsForAction(ProtoInputStream proto, SparseIntArray counts)164     private static void loadCountsForAction(ProtoInputStream proto, SparseIntArray counts)
165             throws IOException {
166         int categoryToken = PackagesTokenData.UNASSIGNED_TOKEN;
167         int count = 0;
168         while (true) {
169             switch (proto.nextField()) {
170                 case (int) UsageStatsObfuscatedProto.ChooserAction.CategoryCount.CATEGORY_TOKEN:
171                     categoryToken = proto.readInt(
172                             UsageStatsObfuscatedProto.ChooserAction.CategoryCount.CATEGORY_TOKEN)
173                             - 1;
174                     break;
175                 case (int) UsageStatsObfuscatedProto.ChooserAction.CategoryCount.COUNT:
176                     count = proto.readInt(
177                             UsageStatsObfuscatedProto.ChooserAction.CategoryCount.COUNT);
178                     break;
179                 case ProtoInputStream.NO_MORE_FIELDS:
180                     if (categoryToken != PackagesTokenData.UNASSIGNED_TOKEN) {
181                         counts.put(categoryToken, count);
182                     }
183                     return;
184             }
185         }
186     }
187 
loadConfigStats(ProtoInputStream proto, IntervalStats stats)188     private static void loadConfigStats(ProtoInputStream proto, IntervalStats stats)
189             throws IOException {
190         boolean configActive = false;
191         final Configuration config = new Configuration();
192         ConfigurationStats configStats = new ConfigurationStats();
193         if (proto.nextField(IntervalStatsObfuscatedProto.Configuration.CONFIG)) {
194             // Fast path; this should work since the configuration is written first
195             config.readFromProto(proto, IntervalStatsObfuscatedProto.Configuration.CONFIG);
196             configStats = stats.getOrCreateConfigurationStats(config);
197         }
198 
199         while (true) {
200             switch (proto.nextField()) {
201                 case (int) IntervalStatsObfuscatedProto.Configuration.CONFIG:
202                     // Fast path failed from some reason, add ConfigStats object to statsOut now
203                     config.readFromProto(proto, IntervalStatsObfuscatedProto.Configuration.CONFIG);
204                     final ConfigurationStats temp = stats.getOrCreateConfigurationStats(config);
205                     temp.mLastTimeActive = configStats.mLastTimeActive;
206                     temp.mTotalTimeActive = configStats.mTotalTimeActive;
207                     temp.mActivationCount = configStats.mActivationCount;
208                     configStats = temp;
209                     break;
210                 case (int) IntervalStatsObfuscatedProto.Configuration.LAST_TIME_ACTIVE_MS:
211                     configStats.mLastTimeActive = stats.beginTime + proto.readLong(
212                             IntervalStatsObfuscatedProto.Configuration.LAST_TIME_ACTIVE_MS);
213                     break;
214                 case (int) IntervalStatsObfuscatedProto.Configuration.TOTAL_TIME_ACTIVE_MS:
215                     configStats.mTotalTimeActive = proto.readLong(
216                             IntervalStatsObfuscatedProto.Configuration.TOTAL_TIME_ACTIVE_MS);
217                     break;
218                 case (int) IntervalStatsObfuscatedProto.Configuration.COUNT:
219                     configStats.mActivationCount = proto.readInt(
220                             IntervalStatsObfuscatedProto.Configuration.COUNT);
221                     break;
222                 case (int) IntervalStatsObfuscatedProto.Configuration.ACTIVE:
223                     configActive = proto.readBoolean(
224                             IntervalStatsObfuscatedProto.Configuration.ACTIVE);
225                     break;
226                 case ProtoInputStream.NO_MORE_FIELDS:
227                     if (configActive) {
228                         stats.activeConfiguration = configStats.mConfiguration;
229                     }
230                     return;
231             }
232         }
233     }
234 
parseEvent(ProtoInputStream proto, long beginTime)235     private static UsageEvents.Event parseEvent(ProtoInputStream proto, long beginTime)
236             throws IOException {
237         final UsageEvents.Event event = new UsageEvents.Event();
238         while (true) {
239             switch (proto.nextField()) {
240                 case (int) EventObfuscatedProto.PACKAGE_TOKEN:
241                     event.mPackageToken = proto.readInt(EventObfuscatedProto.PACKAGE_TOKEN) - 1;
242                     break;
243                 case (int) EventObfuscatedProto.CLASS_TOKEN:
244                     event.mClassToken = proto.readInt(EventObfuscatedProto.CLASS_TOKEN) - 1;
245                     break;
246                 case (int) EventObfuscatedProto.TIME_MS:
247                     event.mTimeStamp = beginTime + proto.readLong(EventObfuscatedProto.TIME_MS);
248                     break;
249                 case (int) EventObfuscatedProto.FLAGS:
250                     event.mFlags = proto.readInt(EventObfuscatedProto.FLAGS);
251                     break;
252                 case (int) EventObfuscatedProto.TYPE:
253                     event.mEventType = proto.readInt(EventObfuscatedProto.TYPE);
254                     break;
255                 case (int) EventObfuscatedProto.CONFIG:
256                     event.mConfiguration = new Configuration();
257                     event.mConfiguration.readFromProto(proto, EventObfuscatedProto.CONFIG);
258                     break;
259                 case (int) EventObfuscatedProto.SHORTCUT_ID_TOKEN:
260                     event.mShortcutIdToken = proto.readInt(
261                             EventObfuscatedProto.SHORTCUT_ID_TOKEN) - 1;
262                     break;
263                 case (int) EventObfuscatedProto.STANDBY_BUCKET:
264                     event.mBucketAndReason = proto.readInt(EventObfuscatedProto.STANDBY_BUCKET);
265                     break;
266                 case (int) EventObfuscatedProto.NOTIFICATION_CHANNEL_ID_TOKEN:
267                     event.mNotificationChannelIdToken = proto.readInt(
268                             EventObfuscatedProto.NOTIFICATION_CHANNEL_ID_TOKEN) - 1;
269                     break;
270                 case (int) EventObfuscatedProto.INSTANCE_ID:
271                     event.mInstanceId = proto.readInt(EventObfuscatedProto.INSTANCE_ID);
272                     break;
273                 case (int) EventObfuscatedProto.TASK_ROOT_PACKAGE_TOKEN:
274                     event.mTaskRootPackageToken = proto.readInt(
275                             EventObfuscatedProto.TASK_ROOT_PACKAGE_TOKEN) - 1;
276                     break;
277                 case (int) EventObfuscatedProto.TASK_ROOT_CLASS_TOKEN:
278                     event.mTaskRootClassToken = proto.readInt(
279                             EventObfuscatedProto.TASK_ROOT_CLASS_TOKEN) - 1;
280                     break;
281                 case (int) EventObfuscatedProto.LOCUS_ID_TOKEN:
282                     event.mLocusIdToken = proto.readInt(
283                             EventObfuscatedProto.LOCUS_ID_TOKEN) - 1;
284                     break;
285                 case ProtoInputStream.NO_MORE_FIELDS:
286                     return event.mPackageToken == PackagesTokenData.UNASSIGNED_TOKEN ? null : event;
287             }
288         }
289     }
290 
writeOffsetTimestamp(ProtoOutputStream proto, long fieldId, long timestamp, long beginTime)291     static void writeOffsetTimestamp(ProtoOutputStream proto, long fieldId,
292             long timestamp, long beginTime) {
293         // timestamps will only be written if they're after the begin time
294         // a grace period of one hour before the begin time is allowed because of rollover logic
295         final long rolloverGracePeriod = beginTime - ONE_HOUR_MS;
296         if (timestamp > rolloverGracePeriod) {
297             // time attributes are stored as an offset of the begin time (given offset)
298             proto.write(fieldId, getOffsetTimestamp(timestamp, beginTime));
299         }
300     }
301 
getOffsetTimestamp(long timestamp, long offset)302     static long getOffsetTimestamp(long timestamp, long offset) {
303         final long offsetTimestamp = timestamp - offset;
304         // add one ms to timestamp if 0 to ensure it's written to proto (default values are ignored)
305         return offsetTimestamp == 0 ? offsetTimestamp + 1 : offsetTimestamp;
306     }
307 
writeUsageStats(ProtoOutputStream proto, final long beginTime, final UsageStats stats)308     private static void writeUsageStats(ProtoOutputStream proto, final long beginTime,
309             final UsageStats stats) throws IllegalArgumentException {
310         proto.write(UsageStatsObfuscatedProto.PACKAGE_TOKEN, stats.mPackageToken + 1);
311         writeOffsetTimestamp(proto, UsageStatsObfuscatedProto.LAST_TIME_ACTIVE_MS,
312                 stats.mLastTimeUsed, beginTime);
313         proto.write(UsageStatsObfuscatedProto.TOTAL_TIME_ACTIVE_MS, stats.mTotalTimeInForeground);
314         writeOffsetTimestamp(proto, UsageStatsObfuscatedProto.LAST_TIME_SERVICE_USED_MS,
315                 stats.mLastTimeForegroundServiceUsed, beginTime);
316         proto.write(UsageStatsObfuscatedProto.TOTAL_TIME_SERVICE_USED_MS,
317                 stats.mTotalTimeForegroundServiceUsed);
318         writeOffsetTimestamp(proto, UsageStatsObfuscatedProto.LAST_TIME_VISIBLE_MS,
319                 stats.mLastTimeVisible, beginTime);
320         proto.write(UsageStatsObfuscatedProto.TOTAL_TIME_VISIBLE_MS, stats.mTotalTimeVisible);
321         writeOffsetTimestamp(proto, UsageStatsObfuscatedProto.LAST_TIME_COMPONENT_USED_MS,
322                 stats.mLastTimeComponentUsed, beginTime);
323         proto.write(UsageStatsObfuscatedProto.APP_LAUNCH_COUNT, stats.mAppLaunchCount);
324         try {
325             writeChooserCounts(proto, stats);
326         } catch (IllegalArgumentException e) {
327             Slog.e(TAG, "Unable to write chooser counts for " + stats.mPackageName, e);
328         }
329     }
330 
writeCountAndTime(ProtoOutputStream proto, long fieldId, int count, long time)331     private static void writeCountAndTime(ProtoOutputStream proto, long fieldId, int count,
332             long time) throws IllegalArgumentException {
333         final long token = proto.start(fieldId);
334         proto.write(IntervalStatsObfuscatedProto.CountAndTime.COUNT, count);
335         proto.write(IntervalStatsObfuscatedProto.CountAndTime.TIME_MS, time);
336         proto.end(token);
337     }
338 
writeChooserCounts(ProtoOutputStream proto, final UsageStats stats)339     private static void writeChooserCounts(ProtoOutputStream proto, final UsageStats stats)
340             throws IllegalArgumentException {
341         if (stats == null || stats.mChooserCountsObfuscated.size() == 0) {
342             return;
343         }
344         final int chooserCountSize = stats.mChooserCountsObfuscated.size();
345         for (int i = 0; i < chooserCountSize; i++) {
346             final int action = stats.mChooserCountsObfuscated.keyAt(i);
347             final SparseIntArray counts = stats.mChooserCountsObfuscated.valueAt(i);
348             if (counts == null || counts.size() == 0) {
349                 continue;
350             }
351             final long token = proto.start(UsageStatsObfuscatedProto.CHOOSER_ACTIONS);
352             proto.write(UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN, action + 1);
353             writeCountsForAction(proto, counts);
354             proto.end(token);
355         }
356     }
357 
writeCountsForAction(ProtoOutputStream proto, SparseIntArray counts)358     private static void writeCountsForAction(ProtoOutputStream proto, SparseIntArray counts)
359             throws IllegalArgumentException {
360         final int countsSize = counts.size();
361         for (int i = 0; i < countsSize; i++) {
362             final int category = counts.keyAt(i);
363             final int count = counts.valueAt(i);
364             if (count <= 0) {
365                 continue;
366             }
367             final long token = proto.start(UsageStatsObfuscatedProto.ChooserAction.COUNTS);
368             proto.write(UsageStatsObfuscatedProto.ChooserAction.CategoryCount.CATEGORY_TOKEN,
369                     category + 1);
370             proto.write(UsageStatsObfuscatedProto.ChooserAction.CategoryCount.COUNT, count);
371             proto.end(token);
372         }
373     }
374 
writeConfigStats(ProtoOutputStream proto, final long statsBeginTime, final ConfigurationStats configStats, boolean isActive)375     private static void writeConfigStats(ProtoOutputStream proto, final long statsBeginTime,
376             final ConfigurationStats configStats, boolean isActive)
377             throws IllegalArgumentException {
378         configStats.mConfiguration.dumpDebug(proto,
379                 IntervalStatsObfuscatedProto.Configuration.CONFIG);
380         writeOffsetTimestamp(proto, IntervalStatsObfuscatedProto.Configuration.LAST_TIME_ACTIVE_MS,
381                 configStats.mLastTimeActive, statsBeginTime);
382         proto.write(IntervalStatsObfuscatedProto.Configuration.TOTAL_TIME_ACTIVE_MS,
383                 configStats.mTotalTimeActive);
384         proto.write(IntervalStatsObfuscatedProto.Configuration.COUNT, configStats.mActivationCount);
385         proto.write(IntervalStatsObfuscatedProto.Configuration.ACTIVE, isActive);
386     }
387 
writeEvent(ProtoOutputStream proto, final long statsBeginTime, final UsageEvents.Event event)388     private static void writeEvent(ProtoOutputStream proto, final long statsBeginTime,
389             final UsageEvents.Event event) throws IllegalArgumentException {
390         proto.write(EventObfuscatedProto.PACKAGE_TOKEN, event.mPackageToken + 1);
391         if (event.mClassToken != PackagesTokenData.UNASSIGNED_TOKEN) {
392             proto.write(EventObfuscatedProto.CLASS_TOKEN, event.mClassToken + 1);
393         }
394         writeOffsetTimestamp(proto, EventObfuscatedProto.TIME_MS, event.mTimeStamp, statsBeginTime);
395         proto.write(EventObfuscatedProto.FLAGS, event.mFlags);
396         proto.write(EventObfuscatedProto.TYPE, event.mEventType);
397         proto.write(EventObfuscatedProto.INSTANCE_ID, event.mInstanceId);
398         if (event.mTaskRootPackageToken != PackagesTokenData.UNASSIGNED_TOKEN) {
399             proto.write(EventObfuscatedProto.TASK_ROOT_PACKAGE_TOKEN,
400                     event.mTaskRootPackageToken + 1);
401         }
402         if (event.mTaskRootClassToken != PackagesTokenData.UNASSIGNED_TOKEN) {
403             proto.write(EventObfuscatedProto.TASK_ROOT_CLASS_TOKEN, event.mTaskRootClassToken + 1);
404         }
405         switch (event.mEventType) {
406             case UsageEvents.Event.CONFIGURATION_CHANGE:
407                 if (event.mConfiguration != null) {
408                     event.mConfiguration.dumpDebug(proto, EventObfuscatedProto.CONFIG);
409                 }
410                 break;
411             case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
412                 if (event.mBucketAndReason != 0) {
413                     proto.write(EventObfuscatedProto.STANDBY_BUCKET, event.mBucketAndReason);
414                 }
415                 break;
416             case UsageEvents.Event.SHORTCUT_INVOCATION:
417                 if (event.mShortcutIdToken != PackagesTokenData.UNASSIGNED_TOKEN) {
418                     proto.write(EventObfuscatedProto.SHORTCUT_ID_TOKEN, event.mShortcutIdToken + 1);
419                 }
420                 break;
421             case UsageEvents.Event.LOCUS_ID_SET:
422                 if (event.mLocusIdToken != PackagesTokenData.UNASSIGNED_TOKEN) {
423                     proto.write(EventObfuscatedProto.LOCUS_ID_TOKEN, event.mLocusIdToken + 1);
424                 }
425                 break;
426             case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
427                 if (event.mNotificationChannelIdToken != PackagesTokenData.UNASSIGNED_TOKEN) {
428                     proto.write(EventObfuscatedProto.NOTIFICATION_CHANNEL_ID_TOKEN,
429                             event.mNotificationChannelIdToken + 1);
430                 }
431                 break;
432         }
433     }
434 
435     /**
436      * Populates a tokenized version of interval stats from the input stream given.
437      *
438      * @param in the input stream from which to read events.
439      * @param stats the interval stats object which will be populated.
440      */
read(InputStream in, IntervalStats stats)441     public static void read(InputStream in, IntervalStats stats) throws IOException {
442         final ProtoInputStream proto = new ProtoInputStream(in);
443         while (true) {
444             switch (proto.nextField()) {
445                 case (int) IntervalStatsObfuscatedProto.END_TIME_MS:
446                     stats.endTime = stats.beginTime + proto.readLong(
447                             IntervalStatsObfuscatedProto.END_TIME_MS);
448                     break;
449                 case (int) IntervalStatsObfuscatedProto.MAJOR_VERSION:
450                     stats.majorVersion = proto.readInt(IntervalStatsObfuscatedProto.MAJOR_VERSION);
451                     break;
452                 case (int) IntervalStatsObfuscatedProto.MINOR_VERSION:
453                     stats.minorVersion = proto.readInt(IntervalStatsObfuscatedProto.MINOR_VERSION);
454                     break;
455                 case (int) IntervalStatsObfuscatedProto.INTERACTIVE:
456                     loadCountAndTime(proto, IntervalStatsObfuscatedProto.INTERACTIVE,
457                             stats.interactiveTracker);
458                     break;
459                 case (int) IntervalStatsObfuscatedProto.NON_INTERACTIVE:
460                     loadCountAndTime(proto, IntervalStatsObfuscatedProto.NON_INTERACTIVE,
461                             stats.nonInteractiveTracker);
462                     break;
463                 case (int) IntervalStatsObfuscatedProto.KEYGUARD_SHOWN:
464                     loadCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_SHOWN,
465                             stats.keyguardShownTracker);
466                     break;
467                 case (int) IntervalStatsObfuscatedProto.KEYGUARD_HIDDEN:
468                     loadCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_HIDDEN,
469                             stats.keyguardHiddenTracker);
470                     break;
471                 case (int) IntervalStatsObfuscatedProto.PACKAGES:
472                     try {
473                         final long packagesToken = proto.start(
474                                 IntervalStatsObfuscatedProto.PACKAGES);
475                         UsageStats usageStats = parseUsageStats(proto, stats.beginTime);
476                         proto.end(packagesToken);
477                         if (usageStats.mPackageToken != PackagesTokenData.UNASSIGNED_TOKEN) {
478                             stats.packageStatsObfuscated.put(usageStats.mPackageToken, usageStats);
479                         }
480                     } catch (IOException e) {
481                         Slog.e(TAG, "Unable to read some usage stats from proto.", e);
482                     }
483                     break;
484                 case (int) IntervalStatsObfuscatedProto.CONFIGURATIONS:
485                     try {
486                         final long configsToken = proto.start(
487                                 IntervalStatsObfuscatedProto.CONFIGURATIONS);
488                         loadConfigStats(proto, stats);
489                         proto.end(configsToken);
490                     } catch (IOException e) {
491                         Slog.e(TAG, "Unable to read some configuration stats from proto.", e);
492                     }
493                     break;
494                 case (int) IntervalStatsObfuscatedProto.EVENT_LOG:
495                     try {
496                         final long eventsToken = proto.start(
497                                 IntervalStatsObfuscatedProto.EVENT_LOG);
498                         UsageEvents.Event event = parseEvent(proto, stats.beginTime);
499                         proto.end(eventsToken);
500                         if (event != null) {
501                             stats.events.insert(event);
502                         }
503                     } catch (IOException e) {
504                         Slog.e(TAG, "Unable to read some events from proto.", e);
505                     }
506                     break;
507                 case ProtoInputStream.NO_MORE_FIELDS:
508                     // update the begin and end time stamps for all usage stats
509                     final int usageStatsSize = stats.packageStatsObfuscated.size();
510                     for (int i = 0; i < usageStatsSize; i++) {
511                         final UsageStats usageStats = stats.packageStatsObfuscated.valueAt(i);
512                         usageStats.mBeginTimeStamp = stats.beginTime;
513                         usageStats.mEndTimeStamp = stats.endTime;
514                     }
515                     return;
516             }
517         }
518     }
519 
520     /**
521      * Writes the tokenized interval stats object to a ProtoBuf file.
522      *
523      * @param out the output stream to which to write the interval stats data.
524      * @param stats the interval stats object to write to the proto file.
525      */
write(OutputStream out, IntervalStats stats)526     public static void write(OutputStream out, IntervalStats stats)
527             throws IOException, IllegalArgumentException {
528         final ProtoOutputStream proto = new ProtoOutputStream(out);
529         proto.write(IntervalStatsObfuscatedProto.END_TIME_MS,
530                 getOffsetTimestamp(stats.endTime, stats.beginTime));
531         proto.write(IntervalStatsObfuscatedProto.MAJOR_VERSION, stats.majorVersion);
532         proto.write(IntervalStatsObfuscatedProto.MINOR_VERSION, stats.minorVersion);
533 
534         try {
535             writeCountAndTime(proto, IntervalStatsObfuscatedProto.INTERACTIVE,
536                     stats.interactiveTracker.count, stats.interactiveTracker.duration);
537             writeCountAndTime(proto, IntervalStatsObfuscatedProto.NON_INTERACTIVE,
538                     stats.nonInteractiveTracker.count, stats.nonInteractiveTracker.duration);
539             writeCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_SHOWN,
540                     stats.keyguardShownTracker.count, stats.keyguardShownTracker.duration);
541             writeCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_HIDDEN,
542                     stats.keyguardHiddenTracker.count, stats.keyguardHiddenTracker.duration);
543         } catch (IllegalArgumentException e) {
544             Slog.e(TAG, "Unable to write some interval stats trackers to proto.", e);
545         }
546 
547         final int statsCount = stats.packageStatsObfuscated.size();
548         for (int i = 0; i < statsCount; i++) {
549             try {
550                 final long token = proto.start(IntervalStatsObfuscatedProto.PACKAGES);
551                 writeUsageStats(proto, stats.beginTime, stats.packageStatsObfuscated.valueAt(i));
552                 proto.end(token);
553             } catch (IllegalArgumentException e) {
554                 Slog.e(TAG, "Unable to write some usage stats to proto.", e);
555             }
556         }
557         final int configCount = stats.configurations.size();
558         for (int i = 0; i < configCount; i++) {
559             boolean active = stats.activeConfiguration.equals(stats.configurations.keyAt(i));
560             try {
561                 final long token = proto.start(IntervalStatsObfuscatedProto.CONFIGURATIONS);
562                 writeConfigStats(proto, stats.beginTime, stats.configurations.valueAt(i), active);
563                 proto.end(token);
564             } catch (IllegalArgumentException e) {
565                 Slog.e(TAG, "Unable to write some configuration stats to proto.", e);
566             }
567         }
568         final int eventCount = stats.events.size();
569         for (int i = 0; i < eventCount; i++) {
570             try {
571                 final long token = proto.start(IntervalStatsObfuscatedProto.EVENT_LOG);
572                 writeEvent(proto, stats.beginTime, stats.events.get(i));
573                 proto.end(token);
574             } catch (IllegalArgumentException e) {
575                 Slog.e(TAG, "Unable to write some events to proto.", e);
576             }
577         }
578 
579         proto.flush();
580     }
581 
582     /***** Read/Write obfuscated packages data logic. *****/
583 
loadPackagesMap(ProtoInputStream proto, SparseArray<ArrayList<String>> tokensToPackagesMap)584     private static void loadPackagesMap(ProtoInputStream proto,
585             SparseArray<ArrayList<String>> tokensToPackagesMap) throws IOException {
586         int key = PackagesTokenData.UNASSIGNED_TOKEN;
587         final ArrayList<String> strings = new ArrayList<>();
588         while (true) {
589             switch (proto.nextField()) {
590                 case (int) ObfuscatedPackagesProto.PackagesMap.PACKAGE_TOKEN:
591                     key = proto.readInt(ObfuscatedPackagesProto.PackagesMap.PACKAGE_TOKEN) - 1;
592                     break;
593                 case (int) ObfuscatedPackagesProto.PackagesMap.STRINGS:
594                     strings.add(proto.readString(ObfuscatedPackagesProto.PackagesMap.STRINGS));
595                     break;
596                 case ProtoInputStream.NO_MORE_FIELDS:
597                     if (key != PackagesTokenData.UNASSIGNED_TOKEN) {
598                         tokensToPackagesMap.put(key, strings);
599                     }
600                     return;
601             }
602         }
603     }
604 
605     /**
606      * Populates the package mappings from the input stream given.
607      *
608      * @param in the input stream from which to read the mappings.
609      * @param packagesTokenData the packages data object to which the data will be read to.
610      */
readObfuscatedData(InputStream in, PackagesTokenData packagesTokenData)611     static void readObfuscatedData(InputStream in, PackagesTokenData packagesTokenData)
612             throws IOException {
613         final ProtoInputStream proto = new ProtoInputStream(in);
614         while (true) {
615             switch (proto.nextField()) {
616                 case (int) ObfuscatedPackagesProto.COUNTER:
617                     packagesTokenData.counter = proto.readInt(ObfuscatedPackagesProto.COUNTER);
618                     break;
619                 case (int) ObfuscatedPackagesProto.PACKAGES_MAP:
620                     final long token = proto.start(ObfuscatedPackagesProto.PACKAGES_MAP);
621                     loadPackagesMap(proto, packagesTokenData.tokensToPackagesMap);
622                     proto.end(token);
623                     break;
624                 case ProtoInputStream.NO_MORE_FIELDS:
625                     return;
626             }
627         }
628     }
629 
630     /**
631      * Writes the packages mapping data to a ProtoBuf file.
632      *
633      * @param out the output stream to which to write the mappings.
634      * @param packagesTokenData the packages data object holding the data to write.
635      */
writeObfuscatedData(OutputStream out, PackagesTokenData packagesTokenData)636     static void writeObfuscatedData(OutputStream out, PackagesTokenData packagesTokenData)
637             throws IOException, IllegalArgumentException {
638         final ProtoOutputStream proto = new ProtoOutputStream(out);
639         proto.write(ObfuscatedPackagesProto.COUNTER, packagesTokenData.counter);
640 
641         final int mapSize = packagesTokenData.tokensToPackagesMap.size();
642         for (int i = 0; i < mapSize; i++) {
643             final long token = proto.start(ObfuscatedPackagesProto.PACKAGES_MAP);
644             int packageToken = packagesTokenData.tokensToPackagesMap.keyAt(i);
645             proto.write(ObfuscatedPackagesProto.PackagesMap.PACKAGE_TOKEN, packageToken + 1);
646 
647             final ArrayList<String> strings = packagesTokenData.tokensToPackagesMap.valueAt(i);
648             final int listSize = strings.size();
649             for (int j = 0; j < listSize; j++) {
650                 proto.write(ObfuscatedPackagesProto.PackagesMap.STRINGS, strings.get(j));
651             }
652             proto.end(token);
653         }
654 
655         proto.flush();
656     }
657 
658     /***** Read/Write pending events logic. *****/
659 
parsePendingEvent(ProtoInputStream proto)660     private static UsageEvents.Event parsePendingEvent(ProtoInputStream proto) throws IOException {
661         final UsageEvents.Event event = new UsageEvents.Event();
662         while (true) {
663             switch (proto.nextField()) {
664                 case (int) PendingEventProto.PACKAGE_NAME:
665                     event.mPackage = proto.readString(PendingEventProto.PACKAGE_NAME);
666                     break;
667                 case (int) PendingEventProto.CLASS_NAME:
668                     event.mClass = proto.readString(PendingEventProto.CLASS_NAME);
669                     break;
670                 case (int) PendingEventProto.TIME_MS:
671                     event.mTimeStamp = proto.readLong(PendingEventProto.TIME_MS);
672                     break;
673                 case (int) PendingEventProto.FLAGS:
674                     event.mFlags = proto.readInt(PendingEventProto.FLAGS);
675                     break;
676                 case (int) PendingEventProto.TYPE:
677                     event.mEventType = proto.readInt(PendingEventProto.TYPE);
678                     break;
679                 case (int) PendingEventProto.CONFIG:
680                     event.mConfiguration = new Configuration();
681                     event.mConfiguration.readFromProto(proto, PendingEventProto.CONFIG);
682                     break;
683                 case (int) PendingEventProto.SHORTCUT_ID:
684                     event.mShortcutId = proto.readString(PendingEventProto.SHORTCUT_ID);
685                     break;
686                 case (int) PendingEventProto.STANDBY_BUCKET:
687                     event.mBucketAndReason = proto.readInt(PendingEventProto.STANDBY_BUCKET);
688                     break;
689                 case (int) PendingEventProto.NOTIFICATION_CHANNEL_ID:
690                     event.mNotificationChannelId = proto.readString(
691                             PendingEventProto.NOTIFICATION_CHANNEL_ID);
692                     break;
693                 case (int) PendingEventProto.INSTANCE_ID:
694                     event.mInstanceId = proto.readInt(PendingEventProto.INSTANCE_ID);
695                     break;
696                 case (int) PendingEventProto.TASK_ROOT_PACKAGE:
697                     event.mTaskRootPackage = proto.readString(PendingEventProto.TASK_ROOT_PACKAGE);
698                     break;
699                 case (int) PendingEventProto.TASK_ROOT_CLASS:
700                     event.mTaskRootClass = proto.readString(PendingEventProto.TASK_ROOT_CLASS);
701                     break;
702                 case ProtoInputStream.NO_MORE_FIELDS:
703                     // Handle default values for certain events types
704                     switch (event.mEventType) {
705                         case UsageEvents.Event.CONFIGURATION_CHANGE:
706                             if (event.mConfiguration == null) {
707                                 event.mConfiguration = new Configuration();
708                             }
709                             break;
710                         case UsageEvents.Event.SHORTCUT_INVOCATION:
711                             if (event.mShortcutId == null) {
712                                 event.mShortcutId = "";
713                             }
714                             break;
715                         case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
716                             if (event.mNotificationChannelId == null) {
717                                 event.mNotificationChannelId = "";
718                             }
719                             break;
720                     }
721                     return event.mPackage == null ? null : event;
722             }
723         }
724     }
725 
726     /**
727      * Populates the list of pending events from the input stream given.
728      *
729      * @param in the input stream from which to read the pending events.
730      * @param events the list of pending events to populate.
731      */
readPendingEvents(InputStream in, LinkedList<UsageEvents.Event> events)732     static void readPendingEvents(InputStream in, LinkedList<UsageEvents.Event> events)
733             throws IOException {
734         final ProtoInputStream proto = new ProtoInputStream(in);
735         while (true) {
736             switch (proto.nextField()) {
737                 case (int) IntervalStatsObfuscatedProto.PENDING_EVENTS:
738                     try {
739                         final long token = proto.start(IntervalStatsObfuscatedProto.PENDING_EVENTS);
740                         UsageEvents.Event event = parsePendingEvent(proto);
741                         proto.end(token);
742                         if (event != null) {
743                             events.add(event);
744                         }
745                     } catch (IOException e) {
746                         Slog.e(TAG, "Unable to parse some pending events from proto.", e);
747                     }
748                     break;
749                 case ProtoInputStream.NO_MORE_FIELDS:
750                     return;
751             }
752         }
753     }
754 
writePendingEvent(ProtoOutputStream proto, UsageEvents.Event event)755     private static void writePendingEvent(ProtoOutputStream proto, UsageEvents.Event event)
756             throws IllegalArgumentException {
757         proto.write(PendingEventProto.PACKAGE_NAME, event.mPackage);
758         if (event.mClass != null) {
759             proto.write(PendingEventProto.CLASS_NAME, event.mClass);
760         }
761         proto.write(PendingEventProto.TIME_MS, event.mTimeStamp);
762         proto.write(PendingEventProto.FLAGS, event.mFlags);
763         proto.write(PendingEventProto.TYPE, event.mEventType);
764         proto.write(PendingEventProto.INSTANCE_ID, event.mInstanceId);
765         if (event.mTaskRootPackage != null) {
766             proto.write(PendingEventProto.TASK_ROOT_PACKAGE, event.mTaskRootPackage);
767         }
768         if (event.mTaskRootClass != null) {
769             proto.write(PendingEventProto.TASK_ROOT_CLASS, event.mTaskRootClass);
770         }
771         switch (event.mEventType) {
772             case UsageEvents.Event.CONFIGURATION_CHANGE:
773                 if (event.mConfiguration != null) {
774                     event.mConfiguration.dumpDebug(proto, PendingEventProto.CONFIG);
775                 }
776                 break;
777             case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
778                 if (event.mBucketAndReason != 0) {
779                     proto.write(PendingEventProto.STANDBY_BUCKET, event.mBucketAndReason);
780                 }
781                 break;
782             case UsageEvents.Event.SHORTCUT_INVOCATION:
783                 if (event.mShortcutId != null) {
784                     proto.write(PendingEventProto.SHORTCUT_ID, event.mShortcutId);
785                 }
786                 break;
787             case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
788                 if (event.mNotificationChannelId != null) {
789                     proto.write(PendingEventProto.NOTIFICATION_CHANNEL_ID,
790                             event.mNotificationChannelId);
791                 }
792                 break;
793         }
794     }
795 
796     /**
797      * Writes the pending events to a ProtoBuf file.
798      *
799      * @param out the output stream to which to write the pending events.
800      * @param events the list of pending events.
801      */
writePendingEvents(OutputStream out, LinkedList<UsageEvents.Event> events)802     static void writePendingEvents(OutputStream out, LinkedList<UsageEvents.Event> events)
803             throws IOException, IllegalArgumentException {
804         final ProtoOutputStream proto = new ProtoOutputStream(out);
805         final int eventCount = events.size();
806         for (int i = 0; i < eventCount; i++) {
807             try {
808                 final long token = proto.start(IntervalStatsObfuscatedProto.PENDING_EVENTS);
809                 writePendingEvent(proto, events.get(i));
810                 proto.end(token);
811             } catch (IllegalArgumentException e) {
812                 Slog.e(TAG, "Unable to write some pending events to proto.", e);
813             }
814         }
815         proto.flush();
816     }
817 
parseGlobalComponentUsage(ProtoInputStream proto)818     private static Pair<String, Long> parseGlobalComponentUsage(ProtoInputStream proto)
819             throws IOException {
820         String packageName = "";
821         long time = 0;
822         while (true) {
823             switch (proto.nextField()) {
824                 case (int) IntervalStatsObfuscatedProto.PackageUsage.PACKAGE_NAME:
825                     packageName = proto.readString(
826                             IntervalStatsObfuscatedProto.PackageUsage.PACKAGE_NAME);
827                     break;
828                 case (int) IntervalStatsObfuscatedProto.PackageUsage.TIME_MS:
829                     time = proto.readLong(IntervalStatsObfuscatedProto.PackageUsage.TIME_MS);
830                     break;
831                 case ProtoInputStream.NO_MORE_FIELDS:
832                     return new Pair<>(packageName, time);
833             }
834         }
835     }
836 
837     /**
838      * Populates the map of latest package usage from the input stream given.
839      *
840      * @param in the input stream from which to read the package usage.
841      * @param lastTimeComponentUsedGlobal the map of package's global component usage to populate.
842      */
readGlobalComponentUsage(InputStream in, Map<String, Long> lastTimeComponentUsedGlobal)843     static void readGlobalComponentUsage(InputStream in,
844             Map<String, Long> lastTimeComponentUsedGlobal) throws IOException {
845         final ProtoInputStream proto = new ProtoInputStream(in);
846         while (true) {
847             switch (proto.nextField()) {
848                 case (int) IntervalStatsObfuscatedProto.PACKAGE_USAGE:
849                     try {
850                         final long token = proto.start(IntervalStatsObfuscatedProto.PACKAGE_USAGE);
851                         final Pair<String, Long> usage = parseGlobalComponentUsage(proto);
852                         proto.end(token);
853                         if (!usage.first.isEmpty() && usage.second > 0) {
854                             lastTimeComponentUsedGlobal.put(usage.first, usage.second);
855                         }
856                     } catch (IOException e) {
857                         Slog.e(TAG, "Unable to parse some package usage from proto.", e);
858                     }
859                     break;
860                 case ProtoInputStream.NO_MORE_FIELDS:
861                     return;
862             }
863         }
864     }
865 
866     /**
867      * Writes the user-agnostic last time package usage to a ProtoBuf file.
868      *
869      * @param out the output stream to which to write the package usage
870      * @param lastTimeComponentUsedGlobal the map storing the global component usage of packages
871      */
writeGlobalComponentUsage(OutputStream out, Map<String, Long> lastTimeComponentUsedGlobal)872     static void writeGlobalComponentUsage(OutputStream out,
873             Map<String, Long> lastTimeComponentUsedGlobal) {
874         final ProtoOutputStream proto = new ProtoOutputStream(out);
875         final Map.Entry<String, Long>[] entries =
876                 (Map.Entry<String, Long>[]) lastTimeComponentUsedGlobal.entrySet().toArray();
877         final int size = entries.length;
878         for (int i = 0; i < size; ++i) {
879             if (entries[i].getValue() <= 0) continue;
880             final long token = proto.start(IntervalStatsObfuscatedProto.PACKAGE_USAGE);
881             proto.write(IntervalStatsObfuscatedProto.PackageUsage.PACKAGE_NAME,
882                     entries[i].getKey());
883             proto.write(IntervalStatsObfuscatedProto.PackageUsage.TIME_MS, entries[i].getValue());
884             proto.end(token);
885         }
886     }
887 }
888