content: /*
   * 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();
      }
  }
  