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