Store time offsets for UsageStats XML

This will make adjusting for time changes easier
in the future.

Change-Id: I49d2dda4cc6dcb1378a58c814849924f585e0417
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java
index 6c80a65..5f639ab 100644
--- a/services/usage/java/com/android/server/usage/IntervalStats.java
+++ b/services/usage/java/com/android/server/usage/IntervalStats.java
@@ -23,13 +23,11 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
-import java.util.ArrayList;
-
 class IntervalStats {
     public long beginTime;
     public long endTime;
     public long lastTimeSaved;
-    public final ArrayMap<String, UsageStats> stats = new ArrayMap<>();
+    public final ArrayMap<String, UsageStats> packageStats = new ArrayMap<>();
     public final ArrayMap<Configuration, ConfigurationStats> configurations = new ArrayMap<>();
     public Configuration activeConfiguration;
     public TimeSparseArray<UsageEvents.Event> events;
@@ -44,13 +42,13 @@
      * Gets the UsageStats object for the given package, or creates one and adds it internally.
      */
     UsageStats getOrCreateUsageStats(String packageName) {
-        UsageStats usageStats = stats.get(packageName);
+        UsageStats usageStats = packageStats.get(packageName);
         if (usageStats == null) {
             usageStats = new UsageStats();
             usageStats.mPackageName = getCachedStringRef(packageName);
             usageStats.mBeginTimeStamp = beginTime;
             usageStats.mEndTimeStamp = endTime;
-            stats.put(usageStats.mPackageName, usageStats);
+            packageStats.put(usageStats.mPackageName, usageStats);
         }
         return usageStats;
     }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index 6c5fa78..62a7ec0 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -35,7 +35,7 @@
  * Provides an interface to query for UsageStat data from an XML database.
  */
 class UsageStatsDatabase {
-    private static final int CURRENT_VERSION = 1;
+    private static final int CURRENT_VERSION = 2;
 
     private static final String TAG = "UsageStatsDatabase";
     private static final boolean DEBUG = UsageStatsService.DEBUG;
@@ -90,7 +90,8 @@
                     }
 
                     for (File f : files) {
-                        mSortedStatFiles[i].put(Long.parseLong(f.getName()), new AtomicFile(f));
+                        final AtomicFile af = new AtomicFile(f);
+                        mSortedStatFiles[i].put(UsageStatsXml.parseBeginTime(af), af);
                     }
                 }
             }
@@ -107,7 +108,7 @@
 
         if (version != CURRENT_VERSION) {
             Slog.i(TAG, "Upgrading from version " + version + " to " + CURRENT_VERSION);
-            doUpgradeLocked(version, CURRENT_VERSION);
+            doUpgradeLocked(version);
 
             try (BufferedWriter writer = new BufferedWriter(new FileWriter(mVersionFile))) {
                 writer.write(Integer.toString(CURRENT_VERSION));
@@ -118,8 +119,8 @@
         }
     }
 
-    private void doUpgradeLocked(int thisVersion, int nextVersion) {
-        if (thisVersion == 0) {
+    private void doUpgradeLocked(int thisVersion) {
+        if (thisVersion < 2) {
             // Delete all files if we are version 0. This is a pre-release version,
             // so this is fine.
             Slog.i(TAG, "Deleting all usage stats files");
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 8aa3d66..2dcdcc4 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -38,6 +38,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArraySet;
@@ -76,6 +77,8 @@
 
     private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
     private File mUsageStatsDir;
+    long mRealTimeSnapshot;
+    long mSystemTimeSnapshot;
 
     public UsageStatsService(Context context) {
         super(context);
@@ -102,6 +105,9 @@
             cleanUpRemovedUsersLocked();
         }
 
+        mRealTimeSnapshot = SystemClock.elapsedRealtime();
+        mSystemTimeSnapshot = System.currentTimeMillis();
+
         publishLocalService(UsageStatsManagerInternal.class, new LocalService());
         publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService());
     }
@@ -380,6 +386,14 @@
      */
     private class LocalService extends UsageStatsManagerInternal {
 
+        /**
+         * The system may have its time change, so at least make sure the events
+         * are monotonic in order.
+         */
+        private long computeMonotonicSystemTime(long realTime) {
+            return (realTime - mRealTimeSnapshot) + mSystemTimeSnapshot;
+        }
+
         @Override
         public void reportEvent(ComponentName component, int userId, int eventType) {
             if (component == null) {
@@ -390,7 +404,7 @@
             UsageEvents.Event event = new UsageEvents.Event();
             event.mPackage = component.getPackageName();
             event.mClass = component.getClassName();
-            event.mTimeStamp = System.currentTimeMillis();
+            event.mTimeStamp = computeMonotonicSystemTime(SystemClock.elapsedRealtime());
             event.mEventType = eventType;
             mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
         }
@@ -404,7 +418,7 @@
 
             UsageEvents.Event event = new UsageEvents.Event();
             event.mPackage = "android";
-            event.mTimeStamp = System.currentTimeMillis();
+            event.mTimeStamp = computeMonotonicSystemTime(SystemClock.elapsedRealtime());
             event.mEventType = UsageEvents.Event.CONFIGURATION_CHANGE;
             event.mConfiguration = new Configuration(config);
             mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXml.java b/services/usage/java/com/android/server/usage/UsageStatsXml.java
index 48881d0..9ce6d63 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsXml.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsXml.java
@@ -37,10 +37,15 @@
     private static final String USAGESTATS_TAG = "usagestats";
     private static final String VERSION_ATTR = "version";
 
+    public static long parseBeginTime(AtomicFile file) {
+        return Long.parseLong(file.getBaseFile().getName());
+    }
+
     public static void read(AtomicFile file, IntervalStats statsOut) throws IOException {
         try {
             FileInputStream in = file.openRead();
             try {
+                statsOut.beginTime = parseBeginTime(file);
                 read(in, statsOut);
                 statsOut.lastTimeSaved = file.getLastModifiedTime();
             } finally {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
index 6529950..ef95a7b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
@@ -15,7 +15,6 @@
  */
 package com.android.server.usage;
 
-import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -26,44 +25,52 @@
 import android.app.usage.TimeSparseArray;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStats;
-import android.content.ComponentName;
 import android.content.res.Configuration;
 
 import java.io.IOException;
 import java.net.ProtocolException;
-import java.util.Locale;
 
 /**
  * UsageStats reader/writer for version 1 of the XML format.
  */
 final class UsageStatsXmlV1 {
+    private static final String PACKAGES_TAG = "packages";
     private static final String PACKAGE_TAG = "package";
-    private static final String CONFIGURATION_TAG = "config";
-    private static final String EVENT_LOG_TAG = "event-log";
 
-    private static final String BEGIN_TIME_ATTR = "beginTime";
-    private static final String END_TIME_ATTR = "endTime";
-    private static final String NAME_ATTR = "name";
+    private static final String CONFIGURATIONS_TAG = "configurations";
+    private static final String CONFIG_TAG = "config";
+
+    private static final String EVENT_LOG_TAG = "event-log";
+    private static final String EVENT_TAG = "event";
+
+    // Attributes
     private static final String PACKAGE_ATTR = "package";
     private static final String CLASS_ATTR = "class";
-    private static final String TOTAL_TIME_ACTIVE_ATTR = "totalTimeActive";
-    private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive";
+    private static final String TOTAL_TIME_ACTIVE_ATTR = "timeActive";
     private static final String COUNT_ATTR = "count";
     private static final String ACTIVE_ATTR = "active";
     private static final String LAST_EVENT_ATTR = "lastEvent";
     private static final String TYPE_ATTR = "type";
+
+    // Time attributes stored as an offset of the beginTime.
+    private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive";
+    private static final String END_TIME_ATTR = "endTime";
     private static final String TIME_ATTR = "time";
 
     private static void loadUsageStats(XmlPullParser parser, IntervalStats statsOut)
             throws XmlPullParserException, IOException {
-        final String name = parser.getAttributeValue(null, NAME_ATTR);
-        if (name == null) {
-            throw new ProtocolException("no " + NAME_ATTR + " attribute present");
+        final String pkg = parser.getAttributeValue(null, PACKAGE_ATTR);
+        if (pkg == null) {
+            throw new ProtocolException("no " + PACKAGE_ATTR + " attribute present");
         }
 
-        UsageStats stats = statsOut.getOrCreateUsageStats(name);
+        final UsageStats stats = statsOut.getOrCreateUsageStats(pkg);
+
+        // Apply the offset to the beginTime to find the absolute time.
+        stats.mLastTimeUsed = statsOut.beginTime + XmlUtils.readLongAttribute(
+                parser, LAST_TIME_ACTIVE_ATTR);
+
         stats.mTotalTimeInForeground = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR);
-        stats.mLastTimeUsed = XmlUtils.readLongAttribute(parser, LAST_TIME_ACTIVE_ATTR);
         stats.mLastEvent = XmlUtils.readIntAttribute(parser, LAST_EVENT_ATTR);
     }
 
@@ -72,8 +79,12 @@
         final Configuration config = new Configuration();
         Configuration.readXmlAttrs(parser, config);
 
-        ConfigurationStats configStats = statsOut.getOrCreateConfigurationStats(config);
-        configStats.mLastTimeActive = XmlUtils.readLongAttribute(parser, LAST_TIME_ACTIVE_ATTR);
+        final ConfigurationStats configStats = statsOut.getOrCreateConfigurationStats(config);
+
+        // Apply the offset to the beginTime to find the absolute time.
+        configStats.mLastTimeActive = statsOut.beginTime + XmlUtils.readLongAttribute(
+                parser, LAST_TIME_ACTIVE_ATTR);
+
         configStats.mTotalTimeActive = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR);
         configStats.mActivationCount = XmlUtils.readIntAttribute(parser, COUNT_ATTR);
         if (XmlUtils.readBooleanAttribute(parser, ACTIVE_ATTR)) {
@@ -83,30 +94,19 @@
 
     private static void loadEvent(XmlPullParser parser, IntervalStats statsOut)
             throws XmlPullParserException, IOException {
-        String packageName = XmlUtils.readStringAttribute(parser, PACKAGE_ATTR);
-        String className;
+        final String packageName = XmlUtils.readStringAttribute(parser, PACKAGE_ATTR);
         if (packageName == null) {
-            // Try getting the component name if it exists.
-            final String componentName = XmlUtils.readStringAttribute(parser, NAME_ATTR);
-            if (componentName == null) {
-                throw new ProtocolException("no " + NAME_ATTR + " or " + PACKAGE_ATTR +
-                        " attribute present");
-            }
-            ComponentName component = ComponentName.unflattenFromString(componentName);
-            if (component == null) {
-                throw new ProtocolException("ComponentName " + componentName + " is invalid");
-            }
-
-            packageName = component.getPackageName();
-            className = component.getClassName();
-        } else {
-            className = XmlUtils.readStringAttribute(parser, CLASS_ATTR);
+            throw new ProtocolException("no " + PACKAGE_ATTR + " attribute present");
         }
 
-        UsageEvents.Event event = statsOut.buildEvent(packageName, className);
-        event.mEventType = XmlUtils.readIntAttribute(parser, TYPE_ATTR);
-        event.mTimeStamp = XmlUtils.readLongAttribute(parser, TIME_ATTR);
+        final String className = XmlUtils.readStringAttribute(parser, CLASS_ATTR);
 
+        final UsageEvents.Event event = statsOut.buildEvent(packageName, className);
+
+        // Apply the offset to the beginTime to find the absolute time of this event.
+        event.mTimeStamp = statsOut.beginTime + XmlUtils.readLongAttribute(parser, TIME_ATTR);
+
+        event.mEventType = XmlUtils.readIntAttribute(parser, TYPE_ATTR);
         if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) {
             event.mConfiguration = new Configuration();
             Configuration.readXmlAttrs(parser, event.mConfiguration);
@@ -118,48 +118,60 @@
         statsOut.events.put(event.mTimeStamp, event);
     }
 
-    private static void writeUsageStats(XmlSerializer xml, final UsageStats stats)
-            throws IOException {
+    private static void writeUsageStats(XmlSerializer xml, final IntervalStats stats,
+            final UsageStats usageStats) throws IOException {
         xml.startTag(null, PACKAGE_TAG);
-        XmlUtils.writeStringAttribute(xml, NAME_ATTR, stats.mPackageName);
-        XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, stats.mTotalTimeInForeground);
-        XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR, stats.mLastTimeUsed);
-        XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, stats.mLastEvent);
+
+        // Write the time offset.
+        XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR,
+                usageStats.mLastTimeUsed - stats.beginTime);
+
+        XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, usageStats.mPackageName);
+        XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, usageStats.mTotalTimeInForeground);
+        XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, usageStats.mLastEvent);
+
         xml.endTag(null, PACKAGE_TAG);
     }
 
-    private static void writeConfigStats(XmlSerializer xml, final ConfigurationStats stats,
-            boolean isActive) throws IOException {
-        xml.startTag(null, CONFIGURATION_TAG);
-        XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR, stats.mLastTimeActive);
-        XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, stats.mTotalTimeActive);
-        XmlUtils.writeIntAttribute(xml, COUNT_ATTR, stats.mActivationCount);
+    private static void writeConfigStats(XmlSerializer xml, final IntervalStats stats,
+            final ConfigurationStats configStats, boolean isActive) throws IOException {
+        xml.startTag(null, CONFIG_TAG);
+
+        // Write the time offset.
+        XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR,
+                configStats.mLastTimeActive - stats.beginTime);
+
+        XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, configStats.mTotalTimeActive);
+        XmlUtils.writeIntAttribute(xml, COUNT_ATTR, configStats.mActivationCount);
         if (isActive) {
             XmlUtils.writeBooleanAttribute(xml, ACTIVE_ATTR, true);
         }
 
         // Now write the attributes representing the configuration object.
-        Configuration.writeXmlAttrs(xml, stats.mConfiguration);
+        Configuration.writeXmlAttrs(xml, configStats.mConfiguration);
 
-        xml.endTag(null, CONFIGURATION_TAG);
+        xml.endTag(null, CONFIG_TAG);
     }
 
-    private static void writeEvent(XmlSerializer xml, final UsageEvents.Event event)
-            throws IOException {
-        xml.startTag(null, EVENT_LOG_TAG);
+    private static void writeEvent(XmlSerializer xml, final IntervalStats stats,
+            final UsageEvents.Event event) throws IOException {
+        xml.startTag(null, EVENT_TAG);
+
+        // Store the time offset.
+        XmlUtils.writeLongAttribute(xml, TIME_ATTR, event.mTimeStamp - stats.beginTime);
+
         XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, event.mPackage);
         if (event.mClass != null) {
             XmlUtils.writeStringAttribute(xml, CLASS_ATTR, event.mClass);
         }
         XmlUtils.writeIntAttribute(xml, TYPE_ATTR, event.mEventType);
-        XmlUtils.writeLongAttribute(xml, TIME_ATTR, event.mTimeStamp);
 
         if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE
                 && event.mConfiguration != null) {
             Configuration.writeXmlAttrs(xml, event.mConfiguration);
         }
 
-        xml.endTag(null, EVENT_LOG_TAG);
+        xml.endTag(null, EVENT_TAG);
     }
 
     /**
@@ -171,7 +183,7 @@
      */
     public static void read(XmlPullParser parser, IntervalStats statsOut)
             throws XmlPullParserException, IOException {
-        statsOut.stats.clear();
+        statsOut.packageStats.clear();
         statsOut.configurations.clear();
         statsOut.activeConfiguration = null;
 
@@ -179,7 +191,6 @@
             statsOut.events.clear();
         }
 
-        statsOut.beginTime = XmlUtils.readLongAttribute(parser, BEGIN_TIME_ATTR);
         statsOut.endTime = XmlUtils.readLongAttribute(parser, END_TIME_ATTR);
 
         int eventCode;
@@ -196,11 +207,11 @@
                     loadUsageStats(parser, statsOut);
                     break;
 
-                case CONFIGURATION_TAG:
+                case CONFIG_TAG:
                     loadConfigStats(parser, statsOut);
                     break;
 
-                case EVENT_LOG_TAG:
+                case EVENT_TAG:
                     loadEvent(parser, statsOut);
                     break;
             }
@@ -208,32 +219,38 @@
     }
 
     /**
-     * Writes the stats object to an XML file. The {@link FastXmlSerializer}
+     * Writes the stats object to an XML file. The {@link XmlSerializer}
      * has already written the <code><usagestats></code> tag, but attributes may still
      * be added.
      *
-     * @param serializer The serializer to which to write the stats data.
+     * @param xml The serializer to which to write the packageStats data.
      * @param stats The stats object to write to the XML file.
      */
-    public static void write(FastXmlSerializer serializer, IntervalStats stats) throws IOException {
-        serializer.attribute(null, BEGIN_TIME_ATTR, Long.toString(stats.beginTime));
-        serializer.attribute(null, END_TIME_ATTR, Long.toString(stats.endTime));
+    public static void write(XmlSerializer xml, IntervalStats stats) throws IOException {
+        XmlUtils.writeLongAttribute(xml, END_TIME_ATTR, stats.endTime - stats.beginTime);
 
-        final int statsCount = stats.stats.size();
+        xml.startTag(null, PACKAGES_TAG);
+        final int statsCount = stats.packageStats.size();
         for (int i = 0; i < statsCount; i++) {
-            writeUsageStats(serializer, stats.stats.valueAt(i));
+            writeUsageStats(xml, stats, stats.packageStats.valueAt(i));
         }
+        xml.endTag(null, PACKAGES_TAG);
 
+
+        xml.startTag(null, CONFIGURATIONS_TAG);
         final int configCount = stats.configurations.size();
         for (int i = 0; i < configCount; i++) {
             boolean active = stats.activeConfiguration.equals(stats.configurations.keyAt(i));
-            writeConfigStats(serializer, stats.configurations.valueAt(i), active);
+            writeConfigStats(xml, stats, stats.configurations.valueAt(i), active);
         }
+        xml.endTag(null, CONFIGURATIONS_TAG);
 
+        xml.startTag(null, EVENT_LOG_TAG);
         final int eventCount = stats.events != null ? stats.events.size() : 0;
         for (int i = 0; i < eventCount; i++) {
-            writeEvent(serializer, stats.events.valueAt(i));
+            writeEvent(xml, stats, stats.events.valueAt(i));
         }
+        xml.endTag(null, EVENT_LOG_TAG);
     }
 
     private UsageStatsXmlV1() {
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 8876495..2769666 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -108,9 +108,9 @@
 
         // Now close off any events that were open at the time this was saved.
         for (IntervalStats stat : mCurrentStats) {
-            final int pkgCount = stat.stats.size();
+            final int pkgCount = stat.packageStats.size();
             for (int i = 0; i < pkgCount; i++) {
-                UsageStats pkgStats = stat.stats.valueAt(i);
+                UsageStats pkgStats = stat.packageStats.valueAt(i);
                 if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
                         pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
                     stat.update(pkgStats.mPackageName, stat.lastTimeSaved,
@@ -168,13 +168,13 @@
                 public void combine(IntervalStats stats, boolean mutable,
                         List<UsageStats> accResult) {
                     if (!mutable) {
-                        accResult.addAll(stats.stats.values());
+                        accResult.addAll(stats.packageStats.values());
                         return;
                     }
 
-                    final int statCount = stats.stats.size();
+                    final int statCount = stats.packageStats.size();
                     for (int i = 0; i < statCount; i++) {
-                        accResult.add(new UsageStats(stats.stats.valueAt(i)));
+                        accResult.add(new UsageStats(stats.packageStats.valueAt(i)));
                     }
                 }
             };
@@ -340,9 +340,9 @@
                 mCurrentStats[UsageStatsManager.INTERVAL_DAILY].activeConfiguration;
         ArraySet<String> continuePreviousDay = new ArraySet<>();
         for (IntervalStats stat : mCurrentStats) {
-            final int pkgCount = stat.stats.size();
+            final int pkgCount = stat.packageStats.size();
             for (int i = 0; i < pkgCount; i++) {
-                UsageStats pkgStats = stat.stats.valueAt(i);
+                UsageStats pkgStats = stat.packageStats.valueAt(i);
                 if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
                         pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
                     continuePreviousDay.add(pkgStats.mPackageName);