blob: 63bf7e7629a9202171c2e4ef1eddc492857652fe [file] [log] [blame]
/*
* Copyright (C) 2018 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.ArrayMap;
import android.util.Slog;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ProtocolException;
import java.util.ArrayList;
import java.util.List;
/**
* UsageStats reader/writer for Protocol Buffer format
*/
final class UsageStatsProto {
private static String TAG = "UsageStatsProto";
// Static-only utility class.
private UsageStatsProto() {}
private static List<String> readStringPool(ProtoInputStream proto) throws IOException {
final long token = proto.start(IntervalStatsProto.STRINGPOOL);
List<String> stringPool;
if (proto.isNextField(IntervalStatsProto.StringPool.SIZE)) {
stringPool = new ArrayList(proto.readInt(IntervalStatsProto.StringPool.SIZE));
} else {
stringPool = new ArrayList();
}
while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
switch (proto.getFieldNumber()) {
case (int) IntervalStatsProto.StringPool.STRINGS:
stringPool.add(proto.readString(IntervalStatsProto.StringPool.STRINGS));
break;
}
}
proto.end(token);
return stringPool;
}
private static void loadUsageStats(ProtoInputStream proto, long fieldId,
IntervalStats statsOut, List<String> stringPool)
throws IOException {
final long token = proto.start(fieldId);
UsageStats stats;
if (proto.isNextField(IntervalStatsProto.UsageStats.PACKAGE_INDEX)) {
// Fast path reading the package name index. Most cases this should work since it is
// written first
stats = statsOut.getOrCreateUsageStats(
stringPool.get(proto.readInt(IntervalStatsProto.UsageStats.PACKAGE_INDEX) - 1));
} else if (proto.isNextField(IntervalStatsProto.UsageStats.PACKAGE)) {
// No package index, try package name instead
stats = statsOut.getOrCreateUsageStats(
proto.readString(IntervalStatsProto.UsageStats.PACKAGE));
} else {
// Temporarily store collected data to a UsageStats object. This is not efficient.
stats = new UsageStats();
}
while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
switch (proto.getFieldNumber()) {
case (int) IntervalStatsProto.UsageStats.PACKAGE:
// Fast track failed from some reason, add UsageStats object to statsOut now
UsageStats tempPackage = statsOut.getOrCreateUsageStats(
proto.readString(IntervalStatsProto.UsageStats.PACKAGE));
tempPackage.mLastTimeUsed = stats.mLastTimeUsed;
tempPackage.mTotalTimeInForeground = stats.mTotalTimeInForeground;
tempPackage.mLastEvent = stats.mLastEvent;
tempPackage.mAppLaunchCount = stats.mAppLaunchCount;
stats = tempPackage;
break;
case (int) IntervalStatsProto.UsageStats.PACKAGE_INDEX:
// Fast track failed from some reason, add UsageStats object to statsOut now
UsageStats tempPackageIndex = statsOut.getOrCreateUsageStats(stringPool.get(
proto.readInt(IntervalStatsProto.UsageStats.PACKAGE_INDEX) - 1));
tempPackageIndex.mLastTimeUsed = stats.mLastTimeUsed;
tempPackageIndex.mTotalTimeInForeground = stats.mTotalTimeInForeground;
tempPackageIndex.mLastEvent = stats.mLastEvent;
tempPackageIndex.mAppLaunchCount = stats.mAppLaunchCount;
stats = tempPackageIndex;
break;
case (int) IntervalStatsProto.UsageStats.LAST_TIME_ACTIVE_MS:
// Time attributes stored is an offset of the beginTime.
stats.mLastTimeUsed = statsOut.beginTime + proto.readLong(
IntervalStatsProto.UsageStats.LAST_TIME_ACTIVE_MS);
break;
case (int) IntervalStatsProto.UsageStats.TOTAL_TIME_ACTIVE_MS:
stats.mTotalTimeInForeground = proto.readLong(
IntervalStatsProto.UsageStats.TOTAL_TIME_ACTIVE_MS);
break;
case (int) IntervalStatsProto.UsageStats.LAST_EVENT:
stats.mLastEvent =
proto.readInt(IntervalStatsProto.UsageStats.LAST_EVENT);
break;
case (int) IntervalStatsProto.UsageStats.APP_LAUNCH_COUNT:
stats.mAppLaunchCount = proto.readInt(
IntervalStatsProto.UsageStats.APP_LAUNCH_COUNT);
break;
case (int) IntervalStatsProto.UsageStats.CHOOSER_ACTIONS:
final long chooserToken = proto.start(
IntervalStatsProto.UsageStats.CHOOSER_ACTIONS);
loadChooserCounts(proto, stats);
proto.end(chooserToken);
break;
case (int) IntervalStatsProto.UsageStats.LAST_TIME_SERVICE_USED_MS:
// Time attributes stored is an offset of the beginTime.
stats.mLastTimeForegroundServiceUsed = statsOut.beginTime + proto.readLong(
IntervalStatsProto.UsageStats.LAST_TIME_SERVICE_USED_MS);
break;
case (int) IntervalStatsProto.UsageStats.TOTAL_TIME_SERVICE_USED_MS:
stats.mTotalTimeForegroundServiceUsed = proto.readLong(
IntervalStatsProto.UsageStats.TOTAL_TIME_SERVICE_USED_MS);
break;
case (int) IntervalStatsProto.UsageStats.LAST_TIME_VISIBLE_MS:
// Time attributes stored is an offset of the beginTime.
stats.mLastTimeVisible = statsOut.beginTime + proto.readLong(
IntervalStatsProto.UsageStats.LAST_TIME_VISIBLE_MS);
break;
case (int) IntervalStatsProto.UsageStats.TOTAL_TIME_VISIBLE_MS:
stats.mTotalTimeVisible = proto.readLong(
IntervalStatsProto.UsageStats.TOTAL_TIME_VISIBLE_MS);
break;
}
}
if (stats.mLastTimeUsed == 0) {
// mLastTimeUsed was not assigned, assume default value of 0 plus beginTime;
stats.mLastTimeUsed = statsOut.beginTime;
}
proto.end(token);
}
private static void loadCountAndTime(ProtoInputStream proto, long fieldId,
IntervalStats.EventTracker tracker) throws IOException {
final long token = proto.start(fieldId);
while (true) {
switch (proto.nextField()) {
case (int) IntervalStatsProto.CountAndTime.COUNT:
tracker.count = proto.readInt(IntervalStatsProto.CountAndTime.COUNT);
break;
case (int) IntervalStatsProto.CountAndTime.TIME_MS:
tracker.duration = proto.readLong(IntervalStatsProto.CountAndTime.TIME_MS);
break;
case ProtoInputStream.NO_MORE_FIELDS:
proto.end(token);
return;
}
}
}
private static void loadChooserCounts(ProtoInputStream proto, UsageStats usageStats)
throws IOException {
if (usageStats.mChooserCounts == null) {
usageStats.mChooserCounts = new ArrayMap<>();
}
String action = null;
ArrayMap<String, Integer> counts;
if (proto.isNextField(IntervalStatsProto.UsageStats.ChooserAction.NAME)) {
// Fast path reading the action name. Most cases this should work since it is written
// first
action = proto.readString(IntervalStatsProto.UsageStats.ChooserAction.NAME);
counts = usageStats.mChooserCounts.get(action);
if (counts == null) {
counts = new ArrayMap<>();
usageStats.mChooserCounts.put(action, counts);
}
} else {
// Temporarily store collected data to an ArrayMap. This is not efficient.
counts = new ArrayMap<>();
}
while (true) {
switch (proto.nextField()) {
case (int) IntervalStatsProto.UsageStats.ChooserAction.NAME:
// Fast path failed from some reason, add the ArrayMap object to usageStats now
action = proto.readString(IntervalStatsProto.UsageStats.ChooserAction.NAME);
usageStats.mChooserCounts.put(action, counts);
break;
case (int) IntervalStatsProto.UsageStats.ChooserAction.COUNTS:
final long token = proto.start(
IntervalStatsProto.UsageStats.ChooserAction.COUNTS);
loadCountsForAction(proto, counts);
proto.end(token);
case ProtoInputStream.NO_MORE_FIELDS:
if (action == null) {
// default string
usageStats.mChooserCounts.put("", counts);
}
return;
}
}
}
private static void loadCountsForAction(ProtoInputStream proto,
ArrayMap<String, Integer> counts) throws IOException {
String category = null;
int count = 0;
while (true) {
switch (proto.nextField()) {
case (int) IntervalStatsProto.UsageStats.ChooserAction.CategoryCount.NAME:
category = proto.readString(
IntervalStatsProto.UsageStats.ChooserAction.CategoryCount.NAME);
break;
case (int) IntervalStatsProto.UsageStats.ChooserAction.CategoryCount.COUNT:
count = proto.readInt(
IntervalStatsProto.UsageStats.ChooserAction.CategoryCount.COUNT);
break;
case ProtoInputStream.NO_MORE_FIELDS:
if (category == null) {
counts.put("", count);
} else {
counts.put(category, count);
}
return;
}
}
}
private static void loadConfigStats(ProtoInputStream proto, long fieldId,
IntervalStats statsOut) throws IOException {
final long token = proto.start(fieldId);
boolean configActive = false;
final Configuration config = new Configuration();
ConfigurationStats configStats;
if (proto.isNextField(IntervalStatsProto.Configuration.CONFIG)) {
// Fast path reading the configuration. Most cases this should work since it is
// written first
config.readFromProto(proto, IntervalStatsProto.Configuration.CONFIG);
configStats = statsOut.getOrCreateConfigurationStats(config);
} else {
// Temporarily store collected data to a ConfigurationStats object. This is not
// efficient.
configStats = new ConfigurationStats();
}
while (true) {
switch (proto.nextField()) {
case (int) IntervalStatsProto.Configuration.CONFIG:
// Fast path failed from some reason, add ConfigStats object to statsOut now
config.readFromProto(proto, IntervalStatsProto.Configuration.CONFIG);
final ConfigurationStats temp = statsOut.getOrCreateConfigurationStats(config);
temp.mLastTimeActive = configStats.mLastTimeActive;
temp.mTotalTimeActive = configStats.mTotalTimeActive;
temp.mActivationCount = configStats.mActivationCount;
configStats = temp;
break;
case (int) IntervalStatsProto.Configuration.LAST_TIME_ACTIVE_MS:
configStats.mLastTimeActive = statsOut.beginTime + proto.readLong(
IntervalStatsProto.Configuration.LAST_TIME_ACTIVE_MS);
break;
case (int) IntervalStatsProto.Configuration.TOTAL_TIME_ACTIVE_MS:
configStats.mTotalTimeActive = proto.readLong(
IntervalStatsProto.Configuration.TOTAL_TIME_ACTIVE_MS);
break;
case (int) IntervalStatsProto.Configuration.COUNT:
configStats.mActivationCount = proto.readInt(
IntervalStatsProto.Configuration.COUNT);
break;
case (int) IntervalStatsProto.Configuration.ACTIVE:
configActive = proto.readBoolean(IntervalStatsProto.Configuration.ACTIVE);
break;
case ProtoInputStream.NO_MORE_FIELDS:
if (configStats.mLastTimeActive == 0) {
//mLastTimeActive was not assigned, assume default value of 0 plus beginTime
configStats.mLastTimeActive = statsOut.beginTime;
}
if (configActive) {
statsOut.activeConfiguration = configStats.mConfiguration;
}
proto.end(token);
return;
}
}
}
private static void loadEvent(ProtoInputStream proto, long fieldId, IntervalStats statsOut,
List<String> stringPool) throws IOException {
final long token = proto.start(fieldId);
UsageEvents.Event event = statsOut.buildEvent(proto, stringPool);
proto.end(token);
if (event.mPackage == null) {
throw new ProtocolException("no package field present");
}
statsOut.events.insert(event);
}
private static void writeStringPool(ProtoOutputStream proto, final IntervalStats stats)
throws IOException {
final long token = proto.start(IntervalStatsProto.STRINGPOOL);
final int size = stats.mStringCache.size();
proto.write(IntervalStatsProto.StringPool.SIZE, size);
for (int i = 0; i < size; i++) {
proto.write(IntervalStatsProto.StringPool.STRINGS, stats.mStringCache.valueAt(i));
}
proto.end(token);
}
private static void writeUsageStats(ProtoOutputStream proto, long fieldId,
final IntervalStats stats, final UsageStats usageStats) throws IOException {
final long token = proto.start(fieldId);
// Write the package name first, so loadUsageStats can avoid creating an extra object
final int packageIndex = stats.mStringCache.indexOf(usageStats.mPackageName);
if (packageIndex >= 0) {
proto.write(IntervalStatsProto.UsageStats.PACKAGE_INDEX, packageIndex + 1);
} else {
// Package not in Stringpool for some reason, write full string instead
Slog.w(TAG, "UsageStats package name (" + usageStats.mPackageName
+ ") not found in IntervalStats string cache");
proto.write(IntervalStatsProto.UsageStats.PACKAGE, usageStats.mPackageName);
}
// Time attributes stored as an offset of the beginTime.
proto.write(IntervalStatsProto.UsageStats.LAST_TIME_ACTIVE_MS,
usageStats.mLastTimeUsed - stats.beginTime);
proto.write(IntervalStatsProto.UsageStats.TOTAL_TIME_ACTIVE_MS,
usageStats.mTotalTimeInForeground);
proto.write(IntervalStatsProto.UsageStats.LAST_EVENT,
usageStats.mLastEvent);
// Time attributes stored as an offset of the beginTime.
proto.write(IntervalStatsProto.UsageStats.LAST_TIME_SERVICE_USED_MS,
usageStats.mLastTimeForegroundServiceUsed - stats.beginTime);
proto.write(IntervalStatsProto.UsageStats.TOTAL_TIME_SERVICE_USED_MS,
usageStats.mTotalTimeForegroundServiceUsed);
// Time attributes stored as an offset of the beginTime.
proto.write(IntervalStatsProto.UsageStats.LAST_TIME_VISIBLE_MS,
usageStats.mLastTimeVisible - stats.beginTime);
proto.write(IntervalStatsProto.UsageStats.TOTAL_TIME_VISIBLE_MS,
usageStats.mTotalTimeVisible);
proto.write(IntervalStatsProto.UsageStats.APP_LAUNCH_COUNT, usageStats.mAppLaunchCount);
writeChooserCounts(proto, usageStats);
proto.end(token);
}
private static void writeCountAndTime(ProtoOutputStream proto, long fieldId, int count,
long time) throws IOException {
final long token = proto.start(fieldId);
proto.write(IntervalStatsProto.CountAndTime.COUNT, count);
proto.write(IntervalStatsProto.CountAndTime.TIME_MS, time);
proto.end(token);
}
private static void writeChooserCounts(ProtoOutputStream proto, final UsageStats usageStats)
throws IOException {
if (usageStats == null || usageStats.mChooserCounts == null
|| usageStats.mChooserCounts.keySet().isEmpty()) {
return;
}
final int chooserCountSize = usageStats.mChooserCounts.size();
for (int i = 0; i < chooserCountSize; i++) {
final String action = usageStats.mChooserCounts.keyAt(i);
final ArrayMap<String, Integer> counts = usageStats.mChooserCounts.valueAt(i);
if (action == null || counts == null || counts.isEmpty()) {
continue;
}
final long token = proto.start(IntervalStatsProto.UsageStats.CHOOSER_ACTIONS);
proto.write(IntervalStatsProto.UsageStats.ChooserAction.NAME, action);
writeCountsForAction(proto, counts);
proto.end(token);
}
}
private static void writeCountsForAction(ProtoOutputStream proto,
ArrayMap<String, Integer> counts) throws IOException {
final int countsSize = counts.size();
for (int i = 0; i < countsSize; i++) {
String key = counts.keyAt(i);
int count = counts.valueAt(i);
if (count > 0) {
final long token = proto.start(IntervalStatsProto.UsageStats.ChooserAction.COUNTS);
proto.write(IntervalStatsProto.UsageStats.ChooserAction.CategoryCount.NAME, key);
proto.write(IntervalStatsProto.UsageStats.ChooserAction.CategoryCount.COUNT, count);
proto.end(token);
}
}
}
private static void writeConfigStats(ProtoOutputStream proto, long fieldId,
final IntervalStats stats, final ConfigurationStats configStats, boolean isActive)
throws IOException {
final long token = proto.start(fieldId);
configStats.mConfiguration.writeToProto(proto, IntervalStatsProto.Configuration.CONFIG);
proto.write(IntervalStatsProto.Configuration.LAST_TIME_ACTIVE_MS,
configStats.mLastTimeActive - stats.beginTime);
proto.write(IntervalStatsProto.Configuration.TOTAL_TIME_ACTIVE_MS,
configStats.mTotalTimeActive);
proto.write(IntervalStatsProto.Configuration.COUNT, configStats.mActivationCount);
proto.write(IntervalStatsProto.Configuration.ACTIVE, isActive);
proto.end(token);
}
private static void writeEvent(ProtoOutputStream proto, long fieldId, final IntervalStats stats,
final UsageEvents.Event event) throws IOException {
final long token = proto.start(fieldId);
final int packageIndex = stats.mStringCache.indexOf(event.mPackage);
if (packageIndex >= 0) {
proto.write(IntervalStatsProto.Event.PACKAGE_INDEX, packageIndex + 1);
} else {
// Package not in Stringpool for some reason, write full string instead
Slog.w(TAG, "Usage event package name (" + event.mPackage
+ ") not found in IntervalStats string cache");
proto.write(IntervalStatsProto.Event.PACKAGE, event.mPackage);
}
if (event.mClass != null) {
final int classIndex = stats.mStringCache.indexOf(event.mClass);
if (classIndex >= 0) {
proto.write(IntervalStatsProto.Event.CLASS_INDEX, classIndex + 1);
} else {
// Class not in Stringpool for some reason, write full string instead
Slog.w(TAG, "Usage event class name (" + event.mClass
+ ") not found in IntervalStats string cache");
proto.write(IntervalStatsProto.Event.CLASS, event.mClass);
}
}
proto.write(IntervalStatsProto.Event.TIME_MS, event.mTimeStamp - stats.beginTime);
proto.write(IntervalStatsProto.Event.FLAGS, event.mFlags);
proto.write(IntervalStatsProto.Event.TYPE, event.mEventType);
proto.write(IntervalStatsProto.Event.INSTANCE_ID, event.mInstanceId);
if (event.mTaskRootPackage != null) {
final int taskRootPackageIndex = stats.mStringCache.indexOf(event.mTaskRootPackage);
if (taskRootPackageIndex >= 0) {
proto.write(IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX,
taskRootPackageIndex + 1);
} else {
// Task root package not in Stringpool for some reason.
Slog.w(TAG, "Usage event task root package name (" + event.mTaskRootPackage
+ ") not found in IntervalStats string cache");
}
}
if (event.mTaskRootClass != null) {
final int taskRootClassIndex = stats.mStringCache.indexOf(event.mTaskRootClass);
if (taskRootClassIndex >= 0) {
proto.write(IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX,
taskRootClassIndex + 1);
} else {
// Task root class not in Stringpool for some reason.
Slog.w(TAG, "Usage event task root class name (" + event.mTaskRootClass
+ ") not found in IntervalStats string cache");
}
}
switch (event.mEventType) {
case UsageEvents.Event.CONFIGURATION_CHANGE:
if (event.mConfiguration != null) {
event.mConfiguration.writeToProto(proto, IntervalStatsProto.Event.CONFIG);
}
break;
case UsageEvents.Event.SHORTCUT_INVOCATION:
if (event.mShortcutId != null) {
proto.write(IntervalStatsProto.Event.SHORTCUT_ID, event.mShortcutId);
}
break;
case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
if (event.mBucketAndReason != 0) {
proto.write(IntervalStatsProto.Event.STANDBY_BUCKET, event.mBucketAndReason);
}
break;
case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
if (event.mNotificationChannelId != null) {
final int channelIndex = stats.mStringCache.indexOf(
event.mNotificationChannelId);
if (channelIndex >= 0) {
proto.write(IntervalStatsProto.Event.NOTIFICATION_CHANNEL_INDEX,
channelIndex + 1);
} else {
// Channel not in Stringpool for some reason, write full string instead
Slog.w(TAG, "Usage event notification channel name ("
+ event.mNotificationChannelId
+ ") not found in IntervalStats string cache");
proto.write(IntervalStatsProto.Event.NOTIFICATION_CHANNEL,
event.mNotificationChannelId);
}
}
break;
}
proto.end(token);
}
/**
* Reads from the {@link ProtoInputStream}.
*
* @param proto The proto from which to read events.
* @param statsOut The stats object to populate with the data from the XML file.
*/
public static void read(InputStream in, IntervalStats statsOut) throws IOException {
final ProtoInputStream proto = new ProtoInputStream(in);
List<String> stringPool = null;
statsOut.packageStats.clear();
statsOut.configurations.clear();
statsOut.activeConfiguration = null;
statsOut.events.clear();
while (true) {
switch (proto.nextField()) {
case (int) IntervalStatsProto.END_TIME_MS:
statsOut.endTime = statsOut.beginTime + proto.readLong(
IntervalStatsProto.END_TIME_MS);
break;
case (int) IntervalStatsProto.MAJOR_VERSION:
statsOut.majorVersion = proto.readInt(
IntervalStatsProto.MAJOR_VERSION);
break;
case (int) IntervalStatsProto.MINOR_VERSION:
statsOut.minorVersion = proto.readInt(
IntervalStatsProto.MINOR_VERSION);
break;
case (int) IntervalStatsProto.INTERACTIVE:
loadCountAndTime(proto, IntervalStatsProto.INTERACTIVE,
statsOut.interactiveTracker);
break;
case (int) IntervalStatsProto.NON_INTERACTIVE:
loadCountAndTime(proto, IntervalStatsProto.NON_INTERACTIVE,
statsOut.nonInteractiveTracker);
break;
case (int) IntervalStatsProto.KEYGUARD_SHOWN:
loadCountAndTime(proto, IntervalStatsProto.KEYGUARD_SHOWN,
statsOut.keyguardShownTracker);
break;
case (int) IntervalStatsProto.KEYGUARD_HIDDEN:
loadCountAndTime(proto, IntervalStatsProto.KEYGUARD_HIDDEN,
statsOut.keyguardHiddenTracker);
break;
case (int) IntervalStatsProto.STRINGPOOL:
stringPool = readStringPool(proto);
statsOut.mStringCache.addAll(stringPool);
break;
case (int) IntervalStatsProto.PACKAGES:
loadUsageStats(proto, IntervalStatsProto.PACKAGES, statsOut, stringPool);
break;
case (int) IntervalStatsProto.CONFIGURATIONS:
loadConfigStats(proto, IntervalStatsProto.CONFIGURATIONS, statsOut);
break;
case (int) IntervalStatsProto.EVENT_LOG:
loadEvent(proto, IntervalStatsProto.EVENT_LOG, statsOut, stringPool);
break;
case ProtoInputStream.NO_MORE_FIELDS:
if (statsOut.endTime == 0) {
// endTime not assigned, assume default value of 0 plus beginTime
statsOut.endTime = statsOut.beginTime;
}
statsOut.upgradeIfNeeded();
return;
}
}
}
/**
* Writes the stats object to an ProtoBuf file.
*
* @param proto The serializer to which to write the packageStats data.
* @param stats The stats object to write to the XML file.
*/
public static void write(OutputStream out, IntervalStats stats) throws IOException {
final ProtoOutputStream proto = new ProtoOutputStream(out);
proto.write(IntervalStatsProto.END_TIME_MS, stats.endTime - stats.beginTime);
proto.write(IntervalStatsProto.MAJOR_VERSION, stats.majorVersion);
proto.write(IntervalStatsProto.MINOR_VERSION, stats.minorVersion);
// String pool should be written before the rest of the usage stats
writeStringPool(proto, stats);
writeCountAndTime(proto, IntervalStatsProto.INTERACTIVE, stats.interactiveTracker.count,
stats.interactiveTracker.duration);
writeCountAndTime(proto, IntervalStatsProto.NON_INTERACTIVE,
stats.nonInteractiveTracker.count, stats.nonInteractiveTracker.duration);
writeCountAndTime(proto, IntervalStatsProto.KEYGUARD_SHOWN,
stats.keyguardShownTracker.count, stats.keyguardShownTracker.duration);
writeCountAndTime(proto, IntervalStatsProto.KEYGUARD_HIDDEN,
stats.keyguardHiddenTracker.count, stats.keyguardHiddenTracker.duration);
final int statsCount = stats.packageStats.size();
for (int i = 0; i < statsCount; i++) {
writeUsageStats(proto, IntervalStatsProto.PACKAGES, stats,
stats.packageStats.valueAt(i));
}
final int configCount = stats.configurations.size();
for (int i = 0; i < configCount; i++) {
boolean active = stats.activeConfiguration.equals(stats.configurations.keyAt(i));
writeConfigStats(proto, IntervalStatsProto.CONFIGURATIONS, stats,
stats.configurations.valueAt(i), active);
}
final int eventCount = stats.events.size();
for (int i = 0; i < eventCount; i++) {
writeEvent(proto, IntervalStatsProto.EVENT_LOG, stats, stats.events.get(i));
}
proto.flush();
}
}