| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.server.usage; |
| |
| import android.app.usage.ConfigurationStats; |
| import android.app.usage.UsageEvents; |
| import android.app.usage.UsageStats; |
| import android.content.res.Configuration; |
| import android.util.Slog; |
| import android.util.SparseArray; |
| import android.util.SparseIntArray; |
| import android.util.proto.ProtoInputStream; |
| import android.util.proto.ProtoOutputStream; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.LinkedList; |
| |
| /** |
| * UsageStats reader/writer V2 for Protocol Buffer format. |
| */ |
| final class UsageStatsProtoV2 { |
| private static final String TAG = "UsageStatsProtoV2"; |
| |
| // Static-only utility class. |
| private UsageStatsProtoV2() {} |
| |
| private static UsageStats parseUsageStats(ProtoInputStream proto, final long beginTime) |
| throws IOException { |
| UsageStats stats = new UsageStats(); |
| // Time attributes stored is an offset of the beginTime. |
| while (true) { |
| switch (proto.nextField()) { |
| case (int) UsageStatsObfuscatedProto.PACKAGE_TOKEN: |
| stats.mPackageToken = proto.readInt( |
| UsageStatsObfuscatedProto.PACKAGE_TOKEN) - 1; |
| break; |
| case (int) UsageStatsObfuscatedProto.LAST_TIME_ACTIVE_MS: |
| stats.mLastTimeUsed = beginTime + proto.readLong( |
| UsageStatsObfuscatedProto.LAST_TIME_ACTIVE_MS); |
| break; |
| case (int) UsageStatsObfuscatedProto.TOTAL_TIME_ACTIVE_MS: |
| stats.mTotalTimeInForeground = proto.readLong( |
| UsageStatsObfuscatedProto.TOTAL_TIME_ACTIVE_MS); |
| break; |
| case (int) UsageStatsObfuscatedProto.APP_LAUNCH_COUNT: |
| stats.mAppLaunchCount = proto.readInt( |
| UsageStatsObfuscatedProto.APP_LAUNCH_COUNT); |
| break; |
| case (int) UsageStatsObfuscatedProto.CHOOSER_ACTIONS: |
| try { |
| final long token = proto.start(UsageStatsObfuscatedProto.CHOOSER_ACTIONS); |
| loadChooserCounts(proto, stats); |
| proto.end(token); |
| } catch (IOException e) { |
| Slog.e(TAG, "Unable to read chooser counts for " + stats.mPackageToken); |
| } |
| break; |
| case (int) UsageStatsObfuscatedProto.LAST_TIME_SERVICE_USED_MS: |
| stats.mLastTimeForegroundServiceUsed = beginTime + proto.readLong( |
| UsageStatsObfuscatedProto.LAST_TIME_SERVICE_USED_MS); |
| break; |
| case (int) UsageStatsObfuscatedProto.TOTAL_TIME_SERVICE_USED_MS: |
| stats.mTotalTimeForegroundServiceUsed = proto.readLong( |
| UsageStatsObfuscatedProto.TOTAL_TIME_SERVICE_USED_MS); |
| break; |
| case (int) UsageStatsObfuscatedProto.LAST_TIME_VISIBLE_MS: |
| stats.mLastTimeVisible = beginTime + proto.readLong( |
| UsageStatsObfuscatedProto.LAST_TIME_VISIBLE_MS); |
| break; |
| case (int) UsageStatsObfuscatedProto.TOTAL_TIME_VISIBLE_MS: |
| stats.mTotalTimeVisible = proto.readLong( |
| UsageStatsObfuscatedProto.TOTAL_TIME_VISIBLE_MS); |
| break; |
| case ProtoInputStream.NO_MORE_FIELDS: |
| // mLastTimeUsed was not read, assume default value of 0 plus beginTime |
| if (stats.mLastTimeUsed == 0) { |
| stats.mLastTimeUsed = beginTime; |
| } |
| return stats; |
| } |
| } |
| } |
| |
| private static void loadCountAndTime(ProtoInputStream proto, long fieldId, |
| IntervalStats.EventTracker tracker) { |
| try { |
| final long token = proto.start(fieldId); |
| while (true) { |
| switch (proto.nextField()) { |
| case (int) IntervalStatsObfuscatedProto.CountAndTime.COUNT: |
| tracker.count = proto.readInt( |
| IntervalStatsObfuscatedProto.CountAndTime.COUNT); |
| break; |
| case (int) IntervalStatsObfuscatedProto.CountAndTime.TIME_MS: |
| tracker.duration = proto.readLong( |
| IntervalStatsObfuscatedProto.CountAndTime.TIME_MS); |
| break; |
| case ProtoInputStream.NO_MORE_FIELDS: |
| proto.end(token); |
| return; |
| } |
| } |
| } catch (IOException e) { |
| Slog.e(TAG, "Unable to read event tracker " + fieldId, e); |
| } |
| } |
| |
| private static void loadChooserCounts(ProtoInputStream proto, UsageStats usageStats) |
| throws IOException { |
| int actionToken; |
| SparseIntArray counts; |
| if (proto.nextField(UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN)) { |
| // Fast path; this should work for most cases since the action token is written first |
| actionToken = proto.readInt(UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN) - 1; |
| counts = usageStats.mChooserCountsObfuscated.get(actionToken); |
| if (counts == null) { |
| counts = new SparseIntArray(); |
| usageStats.mChooserCountsObfuscated.put(actionToken, counts); |
| } |
| } else { |
| counts = new SparseIntArray(); |
| } |
| |
| while (true) { |
| switch (proto.nextField()) { |
| case (int) UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN: |
| // Fast path failed for some reason, add the SparseIntArray object to usageStats |
| actionToken = proto.readInt( |
| UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN) - 1; |
| usageStats.mChooserCountsObfuscated.put(actionToken, counts); |
| break; |
| case (int) UsageStatsObfuscatedProto.ChooserAction.COUNTS: |
| final long token = proto.start(UsageStatsObfuscatedProto.ChooserAction.COUNTS); |
| loadCountsForAction(proto, counts); |
| proto.end(token); |
| break; |
| case ProtoInputStream.NO_MORE_FIELDS: |
| return; // if the action was never read, the loaded counts will be ignored. |
| } |
| } |
| } |
| |
| private static void loadCountsForAction(ProtoInputStream proto, SparseIntArray counts) |
| throws IOException { |
| int categoryToken = PackagesTokenData.UNASSIGNED_TOKEN; |
| int count = 0; |
| while (true) { |
| switch (proto.nextField()) { |
| case (int) UsageStatsObfuscatedProto.ChooserAction.CategoryCount.CATEGORY_TOKEN: |
| categoryToken = proto.readInt( |
| UsageStatsObfuscatedProto.ChooserAction.CategoryCount.CATEGORY_TOKEN) |
| - 1; |
| break; |
| case (int) UsageStatsObfuscatedProto.ChooserAction.CategoryCount.COUNT: |
| count = proto.readInt( |
| UsageStatsObfuscatedProto.ChooserAction.CategoryCount.COUNT); |
| break; |
| case ProtoInputStream.NO_MORE_FIELDS: |
| if (categoryToken != PackagesTokenData.UNASSIGNED_TOKEN) { |
| counts.put(categoryToken, count); |
| } |
| return; |
| } |
| } |
| } |
| |
| private static void loadConfigStats(ProtoInputStream proto, IntervalStats stats) |
| throws IOException { |
| boolean configActive = false; |
| final Configuration config = new Configuration(); |
| ConfigurationStats configStats = new ConfigurationStats(); |
| if (proto.nextField(IntervalStatsObfuscatedProto.Configuration.CONFIG)) { |
| // Fast path; this should work since the configuration is written first |
| config.readFromProto(proto, IntervalStatsObfuscatedProto.Configuration.CONFIG); |
| configStats = stats.getOrCreateConfigurationStats(config); |
| } |
| |
| while (true) { |
| switch (proto.nextField()) { |
| case (int) IntervalStatsObfuscatedProto.Configuration.CONFIG: |
| // Fast path failed from some reason, add ConfigStats object to statsOut now |
| config.readFromProto(proto, IntervalStatsObfuscatedProto.Configuration.CONFIG); |
| final ConfigurationStats temp = stats.getOrCreateConfigurationStats(config); |
| temp.mLastTimeActive = configStats.mLastTimeActive; |
| temp.mTotalTimeActive = configStats.mTotalTimeActive; |
| temp.mActivationCount = configStats.mActivationCount; |
| configStats = temp; |
| break; |
| case (int) IntervalStatsObfuscatedProto.Configuration.LAST_TIME_ACTIVE_MS: |
| configStats.mLastTimeActive = stats.beginTime + proto.readLong( |
| IntervalStatsObfuscatedProto.Configuration.LAST_TIME_ACTIVE_MS); |
| break; |
| case (int) IntervalStatsObfuscatedProto.Configuration.TOTAL_TIME_ACTIVE_MS: |
| configStats.mTotalTimeActive = proto.readLong( |
| IntervalStatsObfuscatedProto.Configuration.TOTAL_TIME_ACTIVE_MS); |
| break; |
| case (int) IntervalStatsObfuscatedProto.Configuration.COUNT: |
| configStats.mActivationCount = proto.readInt( |
| IntervalStatsObfuscatedProto.Configuration.COUNT); |
| break; |
| case (int) IntervalStatsObfuscatedProto.Configuration.ACTIVE: |
| configActive = proto.readBoolean( |
| IntervalStatsObfuscatedProto.Configuration.ACTIVE); |
| break; |
| case ProtoInputStream.NO_MORE_FIELDS: |
| // mLastTimeActive was not assigned, assume default value of 0 plus beginTime |
| if (configStats.mLastTimeActive == 0) { |
| configStats.mLastTimeActive = stats.beginTime; |
| } |
| if (configActive) { |
| stats.activeConfiguration = configStats.mConfiguration; |
| } |
| return; |
| } |
| } |
| } |
| |
| private static UsageEvents.Event parseEvent(ProtoInputStream proto, long beginTime) |
| throws IOException { |
| final UsageEvents.Event event = new UsageEvents.Event(); |
| while (true) { |
| switch (proto.nextField()) { |
| case (int) EventObfuscatedProto.PACKAGE_TOKEN: |
| event.mPackageToken = proto.readInt(EventObfuscatedProto.PACKAGE_TOKEN) - 1; |
| break; |
| case (int) EventObfuscatedProto.CLASS_TOKEN: |
| event.mClassToken = proto.readInt(EventObfuscatedProto.CLASS_TOKEN) - 1; |
| break; |
| case (int) EventObfuscatedProto.TIME_MS: |
| event.mTimeStamp = beginTime + proto.readLong(EventObfuscatedProto.TIME_MS); |
| break; |
| case (int) EventObfuscatedProto.FLAGS: |
| event.mFlags = proto.readInt(EventObfuscatedProto.FLAGS); |
| break; |
| case (int) EventObfuscatedProto.TYPE: |
| event.mEventType = proto.readInt(EventObfuscatedProto.TYPE); |
| break; |
| case (int) EventObfuscatedProto.CONFIG: |
| event.mConfiguration = new Configuration(); |
| event.mConfiguration.readFromProto(proto, EventObfuscatedProto.CONFIG); |
| break; |
| case (int) EventObfuscatedProto.SHORTCUT_ID_TOKEN: |
| event.mShortcutIdToken = proto.readInt( |
| EventObfuscatedProto.SHORTCUT_ID_TOKEN) - 1; |
| break; |
| case (int) EventObfuscatedProto.STANDBY_BUCKET: |
| event.mBucketAndReason = proto.readInt(EventObfuscatedProto.STANDBY_BUCKET); |
| break; |
| case (int) EventObfuscatedProto.NOTIFICATION_CHANNEL_ID_TOKEN: |
| event.mNotificationChannelIdToken = proto.readInt( |
| EventObfuscatedProto.NOTIFICATION_CHANNEL_ID_TOKEN) - 1; |
| break; |
| case (int) EventObfuscatedProto.INSTANCE_ID: |
| event.mInstanceId = proto.readInt(EventObfuscatedProto.INSTANCE_ID); |
| break; |
| case (int) EventObfuscatedProto.TASK_ROOT_PACKAGE_TOKEN: |
| event.mTaskRootPackageToken = proto.readInt( |
| EventObfuscatedProto.TASK_ROOT_PACKAGE_TOKEN) - 1; |
| break; |
| case (int) EventObfuscatedProto.TASK_ROOT_CLASS_TOKEN: |
| event.mTaskRootClassToken = proto.readInt( |
| EventObfuscatedProto.TASK_ROOT_CLASS_TOKEN) - 1; |
| break; |
| case (int) EventObfuscatedProto.LOCUS_ID_TOKEN: |
| event.mLocusIdToken = proto.readInt( |
| EventObfuscatedProto.LOCUS_ID_TOKEN) - 1; |
| break; |
| case ProtoInputStream.NO_MORE_FIELDS: |
| // timeStamp was not read, assume default value 0 plus beginTime |
| if (event.mTimeStamp == 0) { |
| event.mTimeStamp = beginTime; |
| } |
| return event.mPackageToken == PackagesTokenData.UNASSIGNED_TOKEN ? null : event; |
| } |
| } |
| } |
| |
| private static void writeUsageStats(ProtoOutputStream proto, final long beginTime, |
| final UsageStats stats) throws IllegalArgumentException { |
| // Time attributes stored as an offset of the beginTime. |
| proto.write(UsageStatsObfuscatedProto.PACKAGE_TOKEN, stats.mPackageToken + 1); |
| proto.write(UsageStatsObfuscatedProto.LAST_TIME_ACTIVE_MS, stats.mLastTimeUsed - beginTime); |
| proto.write(UsageStatsObfuscatedProto.TOTAL_TIME_ACTIVE_MS, stats.mTotalTimeInForeground); |
| proto.write(UsageStatsObfuscatedProto.LAST_TIME_SERVICE_USED_MS, |
| stats.mLastTimeForegroundServiceUsed - beginTime); |
| proto.write(UsageStatsObfuscatedProto.TOTAL_TIME_SERVICE_USED_MS, |
| stats.mTotalTimeForegroundServiceUsed); |
| proto.write(UsageStatsObfuscatedProto.LAST_TIME_VISIBLE_MS, |
| stats.mLastTimeVisible - beginTime); |
| proto.write(UsageStatsObfuscatedProto.TOTAL_TIME_VISIBLE_MS, stats.mTotalTimeVisible); |
| proto.write(UsageStatsObfuscatedProto.APP_LAUNCH_COUNT, stats.mAppLaunchCount); |
| try { |
| writeChooserCounts(proto, stats); |
| } catch (IllegalArgumentException e) { |
| Slog.e(TAG, "Unable to write chooser counts for " + stats.mPackageName, e); |
| } |
| } |
| |
| private static void writeCountAndTime(ProtoOutputStream proto, long fieldId, int count, |
| long time) throws IllegalArgumentException { |
| final long token = proto.start(fieldId); |
| proto.write(IntervalStatsObfuscatedProto.CountAndTime.COUNT, count); |
| proto.write(IntervalStatsObfuscatedProto.CountAndTime.TIME_MS, time); |
| proto.end(token); |
| } |
| |
| private static void writeChooserCounts(ProtoOutputStream proto, final UsageStats stats) |
| throws IllegalArgumentException { |
| if (stats == null || stats.mChooserCountsObfuscated.size() == 0) { |
| return; |
| } |
| final int chooserCountSize = stats.mChooserCountsObfuscated.size(); |
| for (int i = 0; i < chooserCountSize; i++) { |
| final int action = stats.mChooserCountsObfuscated.keyAt(i); |
| final SparseIntArray counts = stats.mChooserCountsObfuscated.valueAt(i); |
| if (counts == null || counts.size() == 0) { |
| continue; |
| } |
| final long token = proto.start(UsageStatsObfuscatedProto.CHOOSER_ACTIONS); |
| proto.write(UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN, action + 1); |
| writeCountsForAction(proto, counts); |
| proto.end(token); |
| } |
| } |
| |
| private static void writeCountsForAction(ProtoOutputStream proto, SparseIntArray counts) |
| throws IllegalArgumentException { |
| final int countsSize = counts.size(); |
| for (int i = 0; i < countsSize; i++) { |
| final int category = counts.keyAt(i); |
| final int count = counts.valueAt(i); |
| if (count <= 0) { |
| continue; |
| } |
| final long token = proto.start(UsageStatsObfuscatedProto.ChooserAction.COUNTS); |
| proto.write(UsageStatsObfuscatedProto.ChooserAction.CategoryCount.CATEGORY_TOKEN, |
| category + 1); |
| proto.write(UsageStatsObfuscatedProto.ChooserAction.CategoryCount.COUNT, count); |
| proto.end(token); |
| } |
| } |
| |
| private static void writeConfigStats(ProtoOutputStream proto, final long statsBeginTime, |
| final ConfigurationStats configStats, boolean isActive) |
| throws IllegalArgumentException { |
| configStats.mConfiguration.dumpDebug(proto, |
| IntervalStatsObfuscatedProto.Configuration.CONFIG); |
| proto.write(IntervalStatsObfuscatedProto.Configuration.LAST_TIME_ACTIVE_MS, |
| configStats.mLastTimeActive - statsBeginTime); |
| proto.write(IntervalStatsObfuscatedProto.Configuration.TOTAL_TIME_ACTIVE_MS, |
| configStats.mTotalTimeActive); |
| proto.write(IntervalStatsObfuscatedProto.Configuration.COUNT, configStats.mActivationCount); |
| proto.write(IntervalStatsObfuscatedProto.Configuration.ACTIVE, isActive); |
| } |
| |
| private static void writeEvent(ProtoOutputStream proto, final long statsBeginTime, |
| final UsageEvents.Event event) throws IllegalArgumentException { |
| proto.write(EventObfuscatedProto.PACKAGE_TOKEN, event.mPackageToken + 1); |
| if (event.mClassToken != PackagesTokenData.UNASSIGNED_TOKEN) { |
| proto.write(EventObfuscatedProto.CLASS_TOKEN, event.mClassToken + 1); |
| } |
| proto.write(EventObfuscatedProto.TIME_MS, event.mTimeStamp - statsBeginTime); |
| proto.write(EventObfuscatedProto.FLAGS, event.mFlags); |
| proto.write(EventObfuscatedProto.TYPE, event.mEventType); |
| proto.write(EventObfuscatedProto.INSTANCE_ID, event.mInstanceId); |
| if (event.mTaskRootPackageToken != PackagesTokenData.UNASSIGNED_TOKEN) { |
| proto.write(EventObfuscatedProto.TASK_ROOT_PACKAGE_TOKEN, |
| event.mTaskRootPackageToken + 1); |
| } |
| if (event.mTaskRootClassToken != PackagesTokenData.UNASSIGNED_TOKEN) { |
| proto.write(EventObfuscatedProto.TASK_ROOT_CLASS_TOKEN, event.mTaskRootClassToken + 1); |
| } |
| switch (event.mEventType) { |
| case UsageEvents.Event.CONFIGURATION_CHANGE: |
| if (event.mConfiguration != null) { |
| event.mConfiguration.dumpDebug(proto, EventObfuscatedProto.CONFIG); |
| } |
| break; |
| case UsageEvents.Event.STANDBY_BUCKET_CHANGED: |
| if (event.mBucketAndReason != 0) { |
| proto.write(EventObfuscatedProto.STANDBY_BUCKET, event.mBucketAndReason); |
| } |
| break; |
| case UsageEvents.Event.SHORTCUT_INVOCATION: |
| if (event.mShortcutIdToken != PackagesTokenData.UNASSIGNED_TOKEN) { |
| proto.write(EventObfuscatedProto.SHORTCUT_ID_TOKEN, event.mShortcutIdToken + 1); |
| } |
| break; |
| case UsageEvents.Event.LOCUS_ID_SET: |
| if (event.mLocusIdToken != PackagesTokenData.UNASSIGNED_TOKEN) { |
| proto.write(EventObfuscatedProto.LOCUS_ID_TOKEN, event.mLocusIdToken + 1); |
| } |
| break; |
| case UsageEvents.Event.NOTIFICATION_INTERRUPTION: |
| if (event.mNotificationChannelIdToken != PackagesTokenData.UNASSIGNED_TOKEN) { |
| proto.write(EventObfuscatedProto.NOTIFICATION_CHANNEL_ID_TOKEN, |
| event.mNotificationChannelIdToken + 1); |
| } |
| break; |
| } |
| } |
| |
| /** |
| * Populates a tokenized version of interval stats from the input stream given. |
| * |
| * @param in the input stream from which to read events. |
| * @param stats the interval stats object which will be populated. |
| */ |
| public static void read(InputStream in, IntervalStats stats) throws IOException { |
| final ProtoInputStream proto = new ProtoInputStream(in); |
| while (true) { |
| switch (proto.nextField()) { |
| case (int) IntervalStatsObfuscatedProto.END_TIME_MS: |
| stats.endTime = stats.beginTime + proto.readLong( |
| IntervalStatsObfuscatedProto.END_TIME_MS); |
| break; |
| case (int) IntervalStatsObfuscatedProto.MAJOR_VERSION: |
| stats.majorVersion = proto.readInt(IntervalStatsObfuscatedProto.MAJOR_VERSION); |
| break; |
| case (int) IntervalStatsObfuscatedProto.MINOR_VERSION: |
| stats.minorVersion = proto.readInt(IntervalStatsObfuscatedProto.MINOR_VERSION); |
| break; |
| case (int) IntervalStatsObfuscatedProto.INTERACTIVE: |
| loadCountAndTime(proto, IntervalStatsObfuscatedProto.INTERACTIVE, |
| stats.interactiveTracker); |
| break; |
| case (int) IntervalStatsObfuscatedProto.NON_INTERACTIVE: |
| loadCountAndTime(proto, IntervalStatsObfuscatedProto.NON_INTERACTIVE, |
| stats.nonInteractiveTracker); |
| break; |
| case (int) IntervalStatsObfuscatedProto.KEYGUARD_SHOWN: |
| loadCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_SHOWN, |
| stats.keyguardShownTracker); |
| break; |
| case (int) IntervalStatsObfuscatedProto.KEYGUARD_HIDDEN: |
| loadCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_HIDDEN, |
| stats.keyguardHiddenTracker); |
| break; |
| case (int) IntervalStatsObfuscatedProto.PACKAGES: |
| try { |
| final long packagesToken = proto.start( |
| IntervalStatsObfuscatedProto.PACKAGES); |
| UsageStats usageStats = parseUsageStats(proto, stats.beginTime); |
| proto.end(packagesToken); |
| if (usageStats.mPackageToken != PackagesTokenData.UNASSIGNED_TOKEN) { |
| stats.packageStatsObfuscated.put(usageStats.mPackageToken, usageStats); |
| } |
| } catch (IOException e) { |
| Slog.e(TAG, "Unable to read some usage stats from proto.", e); |
| } |
| break; |
| case (int) IntervalStatsObfuscatedProto.CONFIGURATIONS: |
| try { |
| final long configsToken = proto.start( |
| IntervalStatsObfuscatedProto.CONFIGURATIONS); |
| loadConfigStats(proto, stats); |
| proto.end(configsToken); |
| } catch (IOException e) { |
| Slog.e(TAG, "Unable to read some configuration stats from proto.", e); |
| } |
| break; |
| case (int) IntervalStatsObfuscatedProto.EVENT_LOG: |
| try { |
| final long eventsToken = proto.start( |
| IntervalStatsObfuscatedProto.EVENT_LOG); |
| UsageEvents.Event event = parseEvent(proto, stats.beginTime); |
| proto.end(eventsToken); |
| if (event != null) { |
| stats.events.insert(event); |
| } |
| } catch (IOException e) { |
| Slog.e(TAG, "Unable to read some events from proto.", e); |
| } |
| break; |
| case ProtoInputStream.NO_MORE_FIELDS: |
| // endTime not assigned, assume default value of 0 plus beginTime |
| if (stats.endTime == 0) { |
| stats.endTime = stats.beginTime; |
| } |
| // update the begin and end time stamps for all usage stats |
| final int usageStatsSize = stats.packageStatsObfuscated.size(); |
| for (int i = 0; i < usageStatsSize; i++) { |
| final UsageStats usageStats = stats.packageStatsObfuscated.valueAt(i); |
| usageStats.mBeginTimeStamp = stats.beginTime; |
| usageStats.mEndTimeStamp = stats.endTime; |
| } |
| return; |
| } |
| } |
| } |
| |
| /** |
| * Writes the tokenized interval stats object to a ProtoBuf file. |
| * |
| * @param out the output stream to which to write the interval stats data. |
| * @param stats the interval stats object to write to the proto file. |
| */ |
| public static void write(OutputStream out, IntervalStats stats) |
| throws IOException, IllegalArgumentException { |
| final ProtoOutputStream proto = new ProtoOutputStream(out); |
| proto.write(IntervalStatsObfuscatedProto.END_TIME_MS, stats.endTime - stats.beginTime); |
| proto.write(IntervalStatsObfuscatedProto.MAJOR_VERSION, stats.majorVersion); |
| proto.write(IntervalStatsObfuscatedProto.MINOR_VERSION, stats.minorVersion); |
| |
| try { |
| writeCountAndTime(proto, IntervalStatsObfuscatedProto.INTERACTIVE, |
| stats.interactiveTracker.count, stats.interactiveTracker.duration); |
| writeCountAndTime(proto, IntervalStatsObfuscatedProto.NON_INTERACTIVE, |
| stats.nonInteractiveTracker.count, stats.nonInteractiveTracker.duration); |
| writeCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_SHOWN, |
| stats.keyguardShownTracker.count, stats.keyguardShownTracker.duration); |
| writeCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_HIDDEN, |
| stats.keyguardHiddenTracker.count, stats.keyguardHiddenTracker.duration); |
| } catch (IllegalArgumentException e) { |
| Slog.e(TAG, "Unable to write some interval stats trackers to proto.", e); |
| } |
| |
| final int statsCount = stats.packageStatsObfuscated.size(); |
| for (int i = 0; i < statsCount; i++) { |
| try { |
| final long token = proto.start(IntervalStatsObfuscatedProto.PACKAGES); |
| writeUsageStats(proto, stats.beginTime, stats.packageStatsObfuscated.valueAt(i)); |
| proto.end(token); |
| } catch (IllegalArgumentException e) { |
| Slog.e(TAG, "Unable to write some usage stats to proto.", e); |
| } |
| } |
| final int configCount = stats.configurations.size(); |
| for (int i = 0; i < configCount; i++) { |
| boolean active = stats.activeConfiguration.equals(stats.configurations.keyAt(i)); |
| try { |
| final long token = proto.start(IntervalStatsObfuscatedProto.CONFIGURATIONS); |
| writeConfigStats(proto, stats.beginTime, stats.configurations.valueAt(i), active); |
| proto.end(token); |
| } catch (IllegalArgumentException e) { |
| Slog.e(TAG, "Unable to write some configuration stats to proto.", e); |
| } |
| } |
| final int eventCount = stats.events.size(); |
| for (int i = 0; i < eventCount; i++) { |
| try { |
| final long token = proto.start(IntervalStatsObfuscatedProto.EVENT_LOG); |
| writeEvent(proto, stats.beginTime, stats.events.get(i)); |
| proto.end(token); |
| } catch (IllegalArgumentException e) { |
| Slog.e(TAG, "Unable to write some events to proto.", e); |
| } |
| } |
| |
| proto.flush(); |
| } |
| |
| /***** Read/Write obfuscated packages data logic. *****/ |
| |
| private static void loadPackagesMap(ProtoInputStream proto, |
| SparseArray<ArrayList<String>> tokensToPackagesMap) throws IOException { |
| int key = PackagesTokenData.UNASSIGNED_TOKEN; |
| final ArrayList<String> strings = new ArrayList<>(); |
| while (true) { |
| switch (proto.nextField()) { |
| case (int) ObfuscatedPackagesProto.PackagesMap.PACKAGE_TOKEN: |
| key = proto.readInt(ObfuscatedPackagesProto.PackagesMap.PACKAGE_TOKEN) - 1; |
| break; |
| case (int) ObfuscatedPackagesProto.PackagesMap.STRINGS: |
| strings.add(proto.readString(ObfuscatedPackagesProto.PackagesMap.STRINGS)); |
| break; |
| case ProtoInputStream.NO_MORE_FIELDS: |
| if (key != PackagesTokenData.UNASSIGNED_TOKEN) { |
| tokensToPackagesMap.put(key, strings); |
| } |
| return; |
| } |
| } |
| } |
| |
| /** |
| * Populates the package mappings from the input stream given. |
| * |
| * @param in the input stream from which to read the mappings. |
| * @param packagesTokenData the packages data object to which the data will be read to. |
| */ |
| static void readObfuscatedData(InputStream in, PackagesTokenData packagesTokenData) |
| throws IOException { |
| final ProtoInputStream proto = new ProtoInputStream(in); |
| while (true) { |
| switch (proto.nextField()) { |
| case (int) ObfuscatedPackagesProto.COUNTER: |
| packagesTokenData.counter = proto.readInt(ObfuscatedPackagesProto.COUNTER); |
| break; |
| case (int) ObfuscatedPackagesProto.PACKAGES_MAP: |
| final long token = proto.start(ObfuscatedPackagesProto.PACKAGES_MAP); |
| loadPackagesMap(proto, packagesTokenData.tokensToPackagesMap); |
| proto.end(token); |
| break; |
| case ProtoInputStream.NO_MORE_FIELDS: |
| return; |
| } |
| } |
| } |
| |
| /** |
| * Writes the packages mapping data to a ProtoBuf file. |
| * |
| * @param out the output stream to which to write the mappings. |
| * @param packagesTokenData the packages data object holding the data to write. |
| */ |
| static void writeObfuscatedData(OutputStream out, PackagesTokenData packagesTokenData) |
| throws IOException, IllegalArgumentException { |
| final ProtoOutputStream proto = new ProtoOutputStream(out); |
| proto.write(ObfuscatedPackagesProto.COUNTER, packagesTokenData.counter); |
| |
| final int mapSize = packagesTokenData.tokensToPackagesMap.size(); |
| for (int i = 0; i < mapSize; i++) { |
| final long token = proto.start(ObfuscatedPackagesProto.PACKAGES_MAP); |
| int packageToken = packagesTokenData.tokensToPackagesMap.keyAt(i); |
| proto.write(ObfuscatedPackagesProto.PackagesMap.PACKAGE_TOKEN, packageToken + 1); |
| |
| final ArrayList<String> strings = packagesTokenData.tokensToPackagesMap.valueAt(i); |
| final int listSize = strings.size(); |
| for (int j = 0; j < listSize; j++) { |
| proto.write(ObfuscatedPackagesProto.PackagesMap.STRINGS, strings.get(j)); |
| } |
| proto.end(token); |
| } |
| |
| proto.flush(); |
| } |
| |
| /***** Read/Write pending events logic. *****/ |
| |
| private static UsageEvents.Event parsePendingEvent(ProtoInputStream proto) throws IOException { |
| final UsageEvents.Event event = new UsageEvents.Event(); |
| while (true) { |
| switch (proto.nextField()) { |
| case (int) PendingEventProto.PACKAGE_NAME: |
| event.mPackage = proto.readString(PendingEventProto.PACKAGE_NAME); |
| break; |
| case (int) PendingEventProto.CLASS_NAME: |
| event.mClass = proto.readString(PendingEventProto.CLASS_NAME); |
| break; |
| case (int) PendingEventProto.TIME_MS: |
| event.mTimeStamp = proto.readLong(PendingEventProto.TIME_MS); |
| break; |
| case (int) PendingEventProto.FLAGS: |
| event.mFlags = proto.readInt(PendingEventProto.FLAGS); |
| break; |
| case (int) PendingEventProto.TYPE: |
| event.mEventType = proto.readInt(PendingEventProto.TYPE); |
| break; |
| case (int) PendingEventProto.CONFIG: |
| event.mConfiguration = new Configuration(); |
| event.mConfiguration.readFromProto(proto, PendingEventProto.CONFIG); |
| break; |
| case (int) PendingEventProto.SHORTCUT_ID: |
| event.mShortcutId = proto.readString(PendingEventProto.SHORTCUT_ID); |
| break; |
| case (int) PendingEventProto.STANDBY_BUCKET: |
| event.mBucketAndReason = proto.readInt(PendingEventProto.STANDBY_BUCKET); |
| break; |
| case (int) PendingEventProto.NOTIFICATION_CHANNEL_ID: |
| event.mNotificationChannelId = proto.readString( |
| PendingEventProto.NOTIFICATION_CHANNEL_ID); |
| break; |
| case (int) PendingEventProto.INSTANCE_ID: |
| event.mInstanceId = proto.readInt(PendingEventProto.INSTANCE_ID); |
| break; |
| case (int) PendingEventProto.TASK_ROOT_PACKAGE: |
| event.mTaskRootPackage = proto.readString(PendingEventProto.TASK_ROOT_PACKAGE); |
| break; |
| case (int) PendingEventProto.TASK_ROOT_CLASS: |
| event.mTaskRootClass = proto.readString(PendingEventProto.TASK_ROOT_CLASS); |
| break; |
| case ProtoInputStream.NO_MORE_FIELDS: |
| // Handle default values for certain events types |
| switch (event.mEventType) { |
| case UsageEvents.Event.CONFIGURATION_CHANGE: |
| if (event.mConfiguration == null) { |
| event.mConfiguration = new Configuration(); |
| } |
| break; |
| case UsageEvents.Event.SHORTCUT_INVOCATION: |
| if (event.mShortcutId == null) { |
| event.mShortcutId = ""; |
| } |
| break; |
| case UsageEvents.Event.NOTIFICATION_INTERRUPTION: |
| if (event.mNotificationChannelId == null) { |
| event.mNotificationChannelId = ""; |
| } |
| break; |
| } |
| return event.mPackage == null ? null : event; |
| } |
| } |
| } |
| |
| /** |
| * Populates the list of pending events from the input stream given. |
| * |
| * @param in the input stream from which to read the pending events. |
| * @param events the list of pending events to populate. |
| */ |
| static void readPendingEvents(InputStream in, LinkedList<UsageEvents.Event> events) |
| throws IOException { |
| final ProtoInputStream proto = new ProtoInputStream(in); |
| while (true) { |
| switch (proto.nextField()) { |
| case (int) IntervalStatsObfuscatedProto.PENDING_EVENTS: |
| try { |
| final long token = proto.start(IntervalStatsObfuscatedProto.PENDING_EVENTS); |
| UsageEvents.Event event = parsePendingEvent(proto); |
| proto.end(token); |
| if (event != null) { |
| events.add(event); |
| } |
| } catch (IOException e) { |
| Slog.e(TAG, "Unable to parse some pending events from proto.", e); |
| } |
| break; |
| case ProtoInputStream.NO_MORE_FIELDS: |
| return; |
| } |
| } |
| } |
| |
| private static void writePendingEvent(ProtoOutputStream proto, UsageEvents.Event event) |
| throws IllegalArgumentException { |
| proto.write(PendingEventProto.PACKAGE_NAME, event.mPackage); |
| if (event.mClass != null) { |
| proto.write(PendingEventProto.CLASS_NAME, event.mClass); |
| } |
| proto.write(PendingEventProto.TIME_MS, event.mTimeStamp); |
| proto.write(PendingEventProto.FLAGS, event.mFlags); |
| proto.write(PendingEventProto.TYPE, event.mEventType); |
| proto.write(PendingEventProto.INSTANCE_ID, event.mInstanceId); |
| if (event.mTaskRootPackage != null) { |
| proto.write(PendingEventProto.TASK_ROOT_PACKAGE, event.mTaskRootPackage); |
| } |
| if (event.mTaskRootClass != null) { |
| proto.write(PendingEventProto.TASK_ROOT_CLASS, event.mTaskRootClass); |
| } |
| switch (event.mEventType) { |
| case UsageEvents.Event.CONFIGURATION_CHANGE: |
| if (event.mConfiguration != null) { |
| event.mConfiguration.dumpDebug(proto, PendingEventProto.CONFIG); |
| } |
| break; |
| case UsageEvents.Event.STANDBY_BUCKET_CHANGED: |
| if (event.mBucketAndReason != 0) { |
| proto.write(PendingEventProto.STANDBY_BUCKET, event.mBucketAndReason); |
| } |
| break; |
| case UsageEvents.Event.SHORTCUT_INVOCATION: |
| if (event.mShortcutId != null) { |
| proto.write(PendingEventProto.SHORTCUT_ID, event.mShortcutId); |
| } |
| break; |
| case UsageEvents.Event.NOTIFICATION_INTERRUPTION: |
| if (event.mNotificationChannelId != null) { |
| proto.write(PendingEventProto.NOTIFICATION_CHANNEL_ID, |
| event.mNotificationChannelId); |
| } |
| break; |
| } |
| } |
| |
| /** |
| * Writes the pending events to a ProtoBuf file. |
| * |
| * @param out the output stream to which to write the pending events. |
| * @param events the list of pending events. |
| */ |
| static void writePendingEvents(OutputStream out, LinkedList<UsageEvents.Event> events) |
| throws IOException, IllegalArgumentException { |
| final ProtoOutputStream proto = new ProtoOutputStream(out); |
| final int eventCount = events.size(); |
| for (int i = 0; i < eventCount; i++) { |
| try { |
| final long token = proto.start(IntervalStatsObfuscatedProto.PENDING_EVENTS); |
| writePendingEvent(proto, events.get(i)); |
| proto.end(token); |
| } catch (IllegalArgumentException e) { |
| Slog.e(TAG, "Unable to write some pending events to proto.", e); |
| } |
| } |
| proto.flush(); |
| } |
| } |