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