Second iteration of the UsageStats API

Based on feedback from API council, updated the API.
Also added support for querying the event log.

Change-Id: Ibaa008b9e5bd145acdfe8e20c25c2ed2d96be123
diff --git a/api/current.txt b/api/current.txt
index 5f23a36..b6ebd30 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5677,46 +5677,47 @@
 
 package android.app.usage {
 
-  public final class PackageUsageStats implements android.os.Parcelable {
+  public final class UsageEvents implements android.os.Parcelable {
     method public int describeContents();
-    method public long getLastTimeUsed();
-    method public java.lang.String getPackageName();
-    method public long getTotalTimeSpent();
+    method public boolean getNextEvent(android.app.usage.UsageEvents.Event);
+    method public boolean hasNextEvent();
+    method public void resetToStart();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
   }
 
+  public static final class UsageEvents.Event {
+    ctor public UsageEvents.Event();
+    method public android.content.ComponentName getComponent();
+    method public int getEventType();
+    method public long getTimeStamp();
+    field public static final int MOVE_TO_BACKGROUND = 2; // 0x2
+    field public static final int MOVE_TO_FOREGROUND = 1; // 0x1
+    field public static final int NONE = 0; // 0x0
+  }
+
   public final class UsageStats implements android.os.Parcelable {
     ctor public UsageStats(android.app.usage.UsageStats);
+    method public void add(android.app.usage.UsageStats);
     method public int describeContents();
     method public long getFirstTimeStamp();
     method public long getLastTimeStamp();
-    method public android.app.usage.PackageUsageStats getPackage(int);
-    method public android.app.usage.PackageUsageStats getPackage(java.lang.String);
-    method public int getPackageCount();
+    method public long getLastTimeUsed();
+    method public java.lang.String getPackageName();
+    method public long getTotalTimeInForeground();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
   }
 
-  public static class UsageStats.Event implements android.os.Parcelable {
-    ctor public UsageStats.Event();
-    method public int describeContents();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator CREATOR;
-    field public static final int MOVE_TO_BACKGROUND = 2; // 0x2
-    field public static final int MOVE_TO_FOREGROUND = 1; // 0x1
-    field public static final int NONE = 0; // 0x0
-    field public int eventType;
-    field public java.lang.String packageName;
-    field public long timeStamp;
-  }
-
   public final class UsageStatsManager {
-    method public android.app.usage.UsageStats[] getDailyStatsSince(long);
-    method public android.app.usage.UsageStats[] getMonthlyStatsSince(long);
-    method public android.app.usage.UsageStats getRecentStatsSince(long);
-    method public android.app.usage.UsageStats[] getWeeklyStatsSince(long);
-    method public android.app.usage.UsageStats[] getYearlyStatsSince(long);
+    method public android.util.ArrayMap<java.lang.String, android.app.usage.UsageStats> queryAndAggregateUsageStats(long, long);
+    method public android.app.usage.UsageEvents queryEvents(long, long);
+    method public java.util.List<android.app.usage.UsageStats> queryUsageStats(int, long, long);
+    field public static final int INTERVAL_BEST = 4; // 0x4
+    field public static final int INTERVAL_DAILY = 0; // 0x0
+    field public static final int INTERVAL_MONTHLY = 2; // 0x2
+    field public static final int INTERVAL_WEEKLY = 1; // 0x1
+    field public static final int INTERVAL_YEARLY = 3; // 0x3
   }
 
 }
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index 0924210..3b09888 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -16,8 +16,8 @@
 
 package android.app.usage;
 
-import android.app.usage.UsageStats;
-import android.content.ComponentName;
+import android.app.usage.UsageEvents;
+import android.content.pm.ParceledListSlice;
 
 /**
  * System private API for talking with the UsageStatsManagerService.
@@ -25,6 +25,7 @@
  * {@hide}
  */
 interface IUsageStatsManager {
-    UsageStats[] getStatsSince(int bucketType, long time, String callingPackage);
-    UsageStats.Event[] getEventsSince(long time, String callingPackage);
+    ParceledListSlice queryUsageStats(int bucketType, long beginTime, long endTime,
+            String callingPackage);
+    UsageEvents queryEvents(long beginTime, long endTime, String callingPackage);
 }
diff --git a/core/java/android/app/usage/PackageUsageStats.java b/core/java/android/app/usage/PackageUsageStats.java
deleted file mode 100644
index ba4fa21..0000000
--- a/core/java/android/app/usage/PackageUsageStats.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/**
- * Copyright (C) 2014 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 android.app.usage;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-public final class PackageUsageStats implements Parcelable {
-
-    /**
-     * {@hide}
-     */
-    public String mPackageName;
-
-    /**
-     * {@hide}
-     */
-    public long mTotalTimeSpent;
-
-    /**
-     * {@hide}
-     */
-    public long mLastTimeUsed;
-
-    /**
-     * {@hide}
-     */
-    public int mLastEvent;
-
-    PackageUsageStats() {
-    }
-
-    PackageUsageStats(PackageUsageStats stats) {
-        mPackageName = stats.mPackageName;
-        mTotalTimeSpent = stats.mTotalTimeSpent;
-        mLastTimeUsed = stats.mLastTimeUsed;
-        mLastEvent = stats.mLastEvent;
-    }
-
-    public long getTotalTimeSpent() {
-        return mTotalTimeSpent;
-    }
-
-    public long getLastTimeUsed() {
-        return mLastTimeUsed;
-    }
-
-    public String getPackageName() {
-        return mPackageName;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeString(mPackageName);
-        dest.writeLong(mTotalTimeSpent);
-        dest.writeLong(mLastTimeUsed);
-        dest.writeInt(mLastEvent);
-    }
-
-    public static final Creator<PackageUsageStats> CREATOR = new Creator<PackageUsageStats>() {
-        @Override
-        public PackageUsageStats createFromParcel(Parcel in) {
-            PackageUsageStats stats = new PackageUsageStats();
-            stats.mPackageName = in.readString();
-            stats.mTotalTimeSpent = in.readLong();
-            stats.mLastTimeUsed = in.readLong();
-            stats.mLastEvent = in.readInt();
-            return stats;
-        }
-
-        @Override
-        public PackageUsageStats[] newArray(int size) {
-            return new PackageUsageStats[size];
-        }
-    };
-}
diff --git a/core/java/android/app/usage/TimeSparseArray.java b/core/java/android/app/usage/TimeSparseArray.java
index 5a72d02..7974fa7 100644
--- a/core/java/android/app/usage/TimeSparseArray.java
+++ b/core/java/android/app/usage/TimeSparseArray.java
@@ -39,16 +39,16 @@
      * @param time The timestamp for which to search the array.
      * @return The index of the matched element, or -1 if no such match exists.
      */
-    public int closestIndexAfter(long time) {
+    public int closestIndexOnOrAfter(long time) {
         // This is essentially a binary search, except that if no match is found
         // the closest index is returned.
         final int size = size();
         int lo = 0;
-        int hi = size;
+        int hi = size - 1;
         int mid = -1;
         long key = -1;
         while (lo <= hi) {
-            mid = (lo + hi) >>> 1;
+            mid = lo + ((hi - lo) / 2);
             key = keyAt(mid);
 
             if (time > key) {
@@ -68,4 +68,24 @@
             return -1;
         }
     }
+
+    /**
+     * Finds the index of the first element whose timestamp is less than or equal to
+     * the given time.
+     *
+     * @param time The timestamp for which to search the array.
+     * @return The index of the matched element, or -1 if no such match exists.
+     */
+    public int closestIndexOnOrBefore(long time) {
+        final int index = closestIndexOnOrAfter(time);
+        if (index < 0) {
+            // Everything is larger, so we use the last element, or -1 if the list is empty.
+            return size() - 1;
+        }
+
+        if (keyAt(index) == time) {
+            return index;
+        }
+        return index - 1;
+    }
 }
diff --git a/core/java/android/app/usage/UsageStats.aidl b/core/java/android/app/usage/UsageEvents.aidl
similarity index 92%
rename from core/java/android/app/usage/UsageStats.aidl
rename to core/java/android/app/usage/UsageEvents.aidl
index 60dbd1c..f1bceba 100644
--- a/core/java/android/app/usage/UsageStats.aidl
+++ b/core/java/android/app/usage/UsageEvents.aidl
@@ -16,5 +16,4 @@
 
 package android.app.usage;
 
-parcelable UsageStats;
-parcelable UsageStats.Event;
+parcelable UsageEvents;
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
new file mode 100644
index 0000000..d1ebc5f
--- /dev/null
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -0,0 +1,283 @@
+/**
+ * Copyright (C) 2014 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 android.app.usage;
+
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A result returned from {@link android.app.usage.UsageStatsManager#queryEvents(long, long)}
+ * from which to read {@link android.app.usage.UsageEvents.Event} objects.
+ */
+public final class UsageEvents implements Parcelable {
+
+    /**
+     * An event representing a state change for a component.
+     */
+    public static final class Event {
+
+        /**
+         * No event type.
+         */
+        public static final int NONE = 0;
+
+        /**
+         * An event type denoting that a component moved to the foreground.
+         */
+        public static final int MOVE_TO_FOREGROUND = 1;
+
+        /**
+         * An event type denoting that a component moved to the background.
+         */
+        public static final int MOVE_TO_BACKGROUND = 2;
+
+        /**
+         * An event type denoting that a component was in the foreground when the stats
+         * rolled-over. This is effectively treated as a {@link #MOVE_TO_BACKGROUND}.
+         * {@hide}
+         */
+        public static final int END_OF_DAY = 3;
+
+        /**
+         * An event type denoting that a component was in the foreground the previous day.
+         * This is effectively treated as a {@link #MOVE_TO_FOREGROUND}.
+         * {@hide}
+         */
+        public static final int CONTINUE_PREVIOUS_DAY = 4;
+
+        /**
+         * {@hide}
+         */
+        public ComponentName mComponent;
+
+        /**
+         * {@hide}
+         */
+        public long mTimeStamp;
+
+        /**
+         * {@hide}
+         */
+        public int mEventType;
+
+        /**
+         * The component this event represents.
+         */
+        public ComponentName getComponent() {
+            return mComponent;
+        }
+
+        /**
+         * The time at which this event occurred.
+         */
+        public long getTimeStamp() {
+            return mTimeStamp;
+        }
+
+        /**
+         * The event type.
+         *
+         * See {@link #MOVE_TO_BACKGROUND}
+         * See {@link #MOVE_TO_FOREGROUND}
+         */
+        public int getEventType() {
+            return mEventType;
+        }
+    }
+
+    // Only used when creating the resulting events. Not used for reading/unparceling.
+    private List<Event> mEventsToWrite = null;
+
+    // Only used for reading/unparceling events.
+    private Parcel mParcel = null;
+    private final int mEventCount;
+
+    private int mIndex = 0;
+
+    /*
+     * In order to save space, since ComponentNames will be duplicated everywhere,
+     * we use a map and index into it.
+     */
+    private ComponentName[] mComponentNameTable;
+
+    /**
+     * Construct the iterator from a parcel.
+     * {@hide}
+     */
+    public UsageEvents(Parcel in) {
+        mEventCount = in.readInt();
+        mIndex = in.readInt();
+        if (mEventCount > 0) {
+            mComponentNameTable = in.createTypedArray(ComponentName.CREATOR);
+
+            final int listByteLength = in.readInt();
+            final int positionInParcel = in.readInt();
+            mParcel = Parcel.obtain();
+            mParcel.setDataPosition(0);
+            mParcel.appendFrom(in, in.dataPosition(), listByteLength);
+            mParcel.setDataSize(mParcel.dataPosition());
+            mParcel.setDataPosition(positionInParcel);
+        }
+    }
+
+    /**
+     * Create an empty iterator.
+     * {@hide}
+     */
+    UsageEvents() {
+        mEventCount = 0;
+    }
+
+    /**
+     * Construct the iterator in preparation for writing it to a parcel.
+     * {@hide}
+     */
+    public UsageEvents(List<Event> events, ComponentName[] nameTable) {
+        mComponentNameTable = nameTable;
+        mEventCount = events.size();
+        mEventsToWrite = events;
+    }
+
+    /**
+     * Returns whether or not there are more events to read using
+     * {@link #getNextEvent(android.app.usage.UsageEvents.Event)}.
+     *
+     * @return true if there are more events, false otherwise.
+     */
+    public boolean hasNextEvent() {
+        return mIndex < mEventCount;
+    }
+
+    /**
+     * Retrieve the next {@link android.app.usage.UsageEvents.Event} from the collection and put the
+     * resulting data into {@code eventOut}.
+     *
+     * @param eventOut The {@link android.app.usage.UsageEvents.Event} object that will receive the
+     *                 next event data.
+     * @return true if an event was available, false if there are no more events.
+     */
+    public boolean getNextEvent(Event eventOut) {
+        if (mIndex >= mEventCount) {
+            return false;
+        }
+
+        final int index = mParcel.readInt();
+        eventOut.mComponent = mComponentNameTable[index];
+        eventOut.mEventType = mParcel.readInt();
+        eventOut.mTimeStamp = mParcel.readLong();
+        mIndex++;
+
+        if (mIndex >= mEventCount) {
+            mParcel.recycle();
+            mParcel = null;
+        }
+        return true;
+    }
+
+    /**
+     * Resets the collection so that it can be iterated over from the beginning.
+     */
+    public void resetToStart() {
+        mIndex = 0;
+        if (mParcel != null) {
+            mParcel.setDataPosition(0);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mEventCount);
+        dest.writeInt(mIndex);
+        if (mEventCount > 0) {
+            dest.writeTypedArray(mComponentNameTable, flags);
+
+            if (mEventsToWrite != null) {
+                // Write out the events
+                Parcel p = Parcel.obtain();
+                try {
+                    p.setDataPosition(0);
+                    for (int i = 0; i < mEventCount; i++) {
+                        final Event event = mEventsToWrite.get(i);
+
+                        int index = Arrays.binarySearch(mComponentNameTable, event.getComponent());
+                        if (index < 0) {
+                            throw new IllegalStateException(event.getComponent().toShortString() +
+                                    " is not in the component name table");
+                        }
+                        p.writeInt(index);
+                        p.writeInt(event.getEventType());
+                        p.writeLong(event.getTimeStamp());
+                    }
+                    final int listByteLength = p.dataPosition();
+
+                    // Write the total length of the data.
+                    dest.writeInt(listByteLength);
+
+                    // Write our current position into the data.
+                    dest.writeInt(0);
+
+                    // Write the data.
+                    dest.appendFrom(p, 0, listByteLength);
+                } finally {
+                    p.recycle();
+                }
+
+            } else if (mParcel != null) {
+                // Write the total length of the data.
+                dest.writeInt(mParcel.dataSize());
+
+                // Write out current position into the data.
+                dest.writeInt(mParcel.dataPosition());
+
+                // Write the data.
+                dest.appendFrom(mParcel, 0, mParcel.dataSize());
+            } else {
+                throw new IllegalStateException(
+                        "Either mParcel or mEventsToWrite must not be null");
+            }
+        }
+    }
+
+    public static final Creator<UsageEvents> CREATOR = new Creator<UsageEvents>() {
+        @Override
+        public UsageEvents createFromParcel(Parcel source) {
+            return new UsageEvents(source);
+        }
+
+        @Override
+        public UsageEvents[] newArray(int size) {
+            return new UsageEvents[size];
+        }
+    };
+
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        if (mParcel != null) {
+            mParcel.recycle();
+            mParcel = null;
+        }
+    }
+}
diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java
index 57d2011..e47a802 100644
--- a/core/java/android/app/usage/UsageStats.java
+++ b/core/java/android/app/usage/UsageStats.java
@@ -18,76 +18,17 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.ArrayMap;
 
+/**
+ * Contains usage statistics for an app package for a specific
+ * time range.
+ */
 public final class UsageStats implements Parcelable {
-    public static class Event implements Parcelable {
-        /**
-         * {@hide}
-         */
-        public static final Event[] EMPTY_EVENTS = new Event[0];
-
-        public static final int NONE = 0;
-        public static final int MOVE_TO_FOREGROUND = 1;
-        public static final int MOVE_TO_BACKGROUND = 2;
-
-        /**
-         * {@hide}
-         */
-        public static final int END_OF_DAY = 3;
-
-        /**
-         * {@hide}
-         */
-        public static final int CONTINUE_PREVIOUS_DAY = 4;
-
-        public Event() {}
-
-        /**
-         * {@hide}
-         */
-        public Event(String packageName, long timeStamp, int eventType) {
-            this.packageName = packageName;
-            this.timeStamp = timeStamp;
-            this.eventType = eventType;
-        }
-
-        public String packageName;
-        public long timeStamp;
-        public int eventType;
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
-            dest.writeLong(timeStamp);
-            dest.writeInt(eventType);
-            dest.writeString(packageName);
-        }
-
-        public static final Creator<Event> CREATOR = new Creator<Event>() {
-            @Override
-            public Event createFromParcel(Parcel source) {
-                final long time = source.readLong();
-                final int type = source.readInt();
-                final String name = source.readString();
-                return new Event(name, time, type);
-            }
-
-            @Override
-            public Event[] newArray(int size) {
-                return new Event[size];
-            }
-        };
-    }
 
     /**
      * {@hide}
      */
-    public static final UsageStats[] EMPTY_STATS = new UsageStats[0];
+    public String mPackageName;
 
     /**
      * {@hide}
@@ -102,25 +43,22 @@
     /**
      * {@hide}
      */
-    public long mLastTimeSaved;
-
-    private ArrayMap<String, PackageUsageStats> mPackageStats = new ArrayMap<>();
-
-    /**
-     * Can be null
-     * {@hide}
-     */
-    public TimeSparseArray<Event> mEvents;
+    public long mLastTimeUsed;
 
     /**
      * {@hide}
      */
-    public static UsageStats create(long beginTimeStamp, long endTimeStamp) {
-        UsageStats stats = new UsageStats();
-        stats.mBeginTimeStamp = beginTimeStamp;
-        stats.mEndTimeStamp = endTimeStamp;
-        return stats;
-    }
+    public long mTotalTimeInForeground;
+
+    /**
+     * {@hide}
+     */
+    public int mLaunchCount;
+
+    /**
+     * {@hide}
+     */
+    public int mLastEvent;
 
     /**
      * {@hide}
@@ -129,57 +67,68 @@
     }
 
     public UsageStats(UsageStats stats) {
+        mPackageName = stats.mPackageName;
         mBeginTimeStamp = stats.mBeginTimeStamp;
         mEndTimeStamp = stats.mEndTimeStamp;
-        mLastTimeSaved = stats.mLastTimeSaved;
-
-        final int pkgCount = stats.mPackageStats.size();
-        mPackageStats.ensureCapacity(pkgCount);
-        for (int i = 0; i < pkgCount; i++) {
-            PackageUsageStats pkgStats = stats.mPackageStats.valueAt(i);
-            mPackageStats.append(stats.mPackageStats.keyAt(i), new PackageUsageStats(pkgStats));
-        }
-
-        final int eventCount = stats.mEvents == null ? 0 : stats.mEvents.size();
-        if (eventCount > 0) {
-            mEvents = new TimeSparseArray<>();
-            for (int i = 0; i < eventCount; i++) {
-                mEvents.append(stats.mEvents.keyAt(i), stats.mEvents.valueAt(i));
-            }
-        }
+        mLastTimeUsed = stats.mLastTimeUsed;
+        mTotalTimeInForeground = stats.mTotalTimeInForeground;
+        mLaunchCount = stats.mLaunchCount;
+        mLastEvent = stats.mLastEvent;
     }
 
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Get the beginning of the time range this {@link android.app.usage.UsageStats} represents.
+     */
     public long getFirstTimeStamp() {
         return mBeginTimeStamp;
     }
 
+    /**
+     * Get the end of the time range this {@link android.app.usage.UsageStats} represents.
+     */
     public long getLastTimeStamp() {
         return mEndTimeStamp;
     }
 
-    public int getPackageCount() {
-        return mPackageStats.size();
-    }
-
-    public PackageUsageStats getPackage(int index) {
-        return mPackageStats.valueAt(index);
-    }
-
-    public PackageUsageStats getPackage(String packageName) {
-        return mPackageStats.get(packageName);
+    /**
+     * Get the last time this package was used.
+     */
+    public long getLastTimeUsed() {
+        return mLastTimeUsed;
     }
 
     /**
-     * {@hide}
+     * Get the total time this package spent in the foreground.
      */
-    public PackageUsageStats getOrCreatePackageUsageStats(String packageName) {
-        PackageUsageStats pkgStats = mPackageStats.get(packageName);
-        if (pkgStats == null) {
-            pkgStats = new PackageUsageStats();
-            pkgStats.mPackageName = packageName;
-            mPackageStats.put(packageName, pkgStats);
+    public long getTotalTimeInForeground() {
+        return mTotalTimeInForeground;
+    }
+
+    /**
+     * Add the statistics from the right {@link UsageStats} to the left. The package name for
+     * both {@link UsageStats} objects must be the same.
+     * @param right The {@link UsageStats} object to merge into this one.
+     * @throws java.lang.IllegalArgumentException if the package names of the two
+     *         {@link UsageStats} objects are different.
+     */
+    public void add(UsageStats right) {
+        if (!mPackageName.equals(right.mPackageName)) {
+            throw new IllegalArgumentException("Can't merge UsageStats for package '" +
+                    mPackageName + "' with UsageStats for package '" + right.mPackageName + "'.");
         }
-        return pkgStats;
+
+        if (right.mEndTimeStamp > mEndTimeStamp) {
+            mLastEvent = right.mLastEvent;
+            mEndTimeStamp = right.mEndTimeStamp;
+            mLastTimeUsed = right.mLastTimeUsed;
+        }
+        mBeginTimeStamp = Math.min(mBeginTimeStamp, right.mBeginTimeStamp);
+        mTotalTimeInForeground += right.mTotalTimeInForeground;
+        mLaunchCount += right.mLaunchCount;
     }
 
     @Override
@@ -189,47 +138,26 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mPackageName);
         dest.writeLong(mBeginTimeStamp);
         dest.writeLong(mEndTimeStamp);
-        dest.writeLong(mLastTimeSaved);
-
-        int size = mPackageStats.size();
-        dest.writeInt(size);
-        for (int i = 0; i < size; i++) {
-            mPackageStats.valueAt(i).writeToParcel(dest, flags);
-        }
-
-        size = mEvents == null ? 0 : mEvents.size();
-        dest.writeInt(size);
-        for (int i = 0; i < size; i++) {
-            mEvents.valueAt(i).writeToParcel(dest, flags);
-        }
+        dest.writeLong(mLastTimeUsed);
+        dest.writeLong(mTotalTimeInForeground);
+        dest.writeInt(mLaunchCount);
+        dest.writeInt(mLastEvent);
     }
 
     public static final Creator<UsageStats> CREATOR = new Creator<UsageStats>() {
         @Override
         public UsageStats createFromParcel(Parcel in) {
             UsageStats stats = new UsageStats();
+            stats.mPackageName = in.readString();
             stats.mBeginTimeStamp = in.readLong();
             stats.mEndTimeStamp = in.readLong();
-            stats.mLastTimeSaved = in.readLong();
-
-            int size = in.readInt();
-            stats.mPackageStats.ensureCapacity(size);
-            for (int i = 0; i < size; i++) {
-                final PackageUsageStats pkgStats = PackageUsageStats.CREATOR.createFromParcel(in);
-                stats.mPackageStats.put(pkgStats.mPackageName, pkgStats);
-            }
-
-            size = in.readInt();
-            if (size > 0) {
-                stats.mEvents = new TimeSparseArray<>(size);
-                for (int i = 0; i < size; i++) {
-                    final Event event = Event.CREATOR.createFromParcel(in);
-                    stats.mEvents.put(event.timeStamp, event);
-                }
-            }
-
+            stats.mLastTimeUsed = in.readLong();
+            stats.mTotalTimeInForeground = in.readLong();
+            stats.mLaunchCount = in.readInt();
+            stats.mLastEvent = in.readInt();
             return stats;
         }
 
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index fe02637..f9b8928 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -17,33 +17,73 @@
 package android.app.usage;
 
 import android.content.Context;
+import android.content.pm.ParceledListSlice;
 import android.os.RemoteException;
+import android.util.ArrayMap;
 
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Provides access to device usage history and statistics. Usage data is aggregated into
+ * time intervals: days, weeks, months, and years.
+ * <p />
+ * When requesting usage data since a particular time, the request might look something like this:
+ * <pre>
+ * PAST                   REQUEST_TIME                    TODAY                   FUTURE
+ * ————————————————————————————||———————————————————————————¦-----------------------|
+ *                        YEAR ||                           ¦                       |
+ * ————————————————————————————||———————————————————————————¦-----------------------|
+ *  MONTH            |         ||                MONTH      ¦                       |
+ * ——————————————————|—————————||———————————————————————————¦-----------------------|
+ *   |      WEEK     |     WEEK||    |     WEEK     |     WE¦EK     |      WEEK     |
+ * ————————————————————————————||———————————————————|———————¦-----------------------|
+ *                             ||           |DAY|DAY|DAY|DAY¦DAY|DAY|DAY|DAY|DAY|DAY|
+ * ————————————————————————————||———————————————————————————¦-----------------------|
+ * </pre>
+ * A request for data in the middle of a time interval will include that interval.
+ * <p/>
+ * <b>NOTE:</b> This API requires the permission android.permission.PACKAGE_USAGE_STATS, which
+ * is a system-level permission and will not be granted to third-party apps. However, declaring
+ * the permission implies intention to use the API and the user of the device can grant permission
+ * through the Settings application.
+ */
 public final class UsageStatsManager {
-    /**
-     * {@hide}
-     */
-    public static final int DAILY_BUCKET = 0;
 
     /**
-     * {@hide}
+     * An interval type that spans a day. See {@link #queryUsageStats(int, long, long)}.
      */
-    public static final int WEEKLY_BUCKET = 1;
+    public static final int INTERVAL_DAILY = 0;
 
     /**
-     * {@hide}
+     * An interval type that spans a week. See {@link #queryUsageStats(int, long, long)}.
      */
-    public static final int MONTHLY_BUCKET = 2;
+    public static final int INTERVAL_WEEKLY = 1;
 
     /**
-     * {@hide}
+     * An interval type that spans a month. See {@link #queryUsageStats(int, long, long)}.
      */
-    public static final int YEARLY_BUCKET = 3;
+    public static final int INTERVAL_MONTHLY = 2;
 
     /**
+     * An interval type that spans a year. See {@link #queryUsageStats(int, long, long)}.
+     */
+    public static final int INTERVAL_YEARLY = 3;
+
+    /**
+     * An interval type that will use the best fit interval for the given time range.
+     * See {@link #queryUsageStats(int, long, long)}.
+     */
+    public static final int INTERVAL_BEST = 4;
+
+    /**
+     * The number of available intervals. Does not include {@link #INTERVAL_BEST}, since it
+     * is a pseudo interval (it actually selects a real interval).
      * {@hide}
      */
-    public static final int BUCKET_COUNT = 4;
+    public static final int INTERVAL_COUNT = 4;
+
+    private static final UsageEvents sEmptyResults = new UsageEvents();
 
     private final Context mContext;
     private final IUsageStatsManager mService;
@@ -56,67 +96,100 @@
         mService = service;
     }
 
-    public UsageStats[] getDailyStatsSince(long time) {
+    /**
+     * Gets application usage stats for the given time range, aggregated by the specified interval.
+     * <p>The returned list will contain a {@link UsageStats} object for each package that
+     * has data for an interval that is a subset of the time range given. To illustrate:</p>
+     * <pre>
+     * intervalType = INTERVAL_YEARLY
+     * beginTime = 2013
+     * endTime = 2015 (exclusive)
+     *
+     * Results:
+     * 2013 - com.example.alpha
+     * 2013 - com.example.beta
+     * 2014 - com.example.alpha
+     * 2014 - com.example.beta
+     * 2014 - com.example.charlie
+     * </pre>
+     *
+     * @param intervalType The time interval by which the stats are aggregated.
+     * @param beginTime The inclusive beginning of the range of stats to include in the results.
+     * @param endTime The exclusive end of the range of stats to include in the results.
+     * @return A list of {@link UsageStats} or null if none are available.
+     *
+     * @see #INTERVAL_DAILY
+     * @see #INTERVAL_WEEKLY
+     * @see #INTERVAL_MONTHLY
+     * @see #INTERVAL_YEARLY
+     * @see #INTERVAL_BEST
+     */
+    @SuppressWarnings("unchecked")
+    public List<UsageStats> queryUsageStats(int intervalType, long beginTime, long endTime) {
         try {
-            return mService.getStatsSince(DAILY_BUCKET, time, mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            return null;
-        }
-    }
-
-    public UsageStats[] getWeeklyStatsSince(long time) {
-        try {
-            return mService.getStatsSince(WEEKLY_BUCKET, time, mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            return null;
-        }
-    }
-
-    public UsageStats[] getMonthlyStatsSince(long time) {
-        try {
-            return mService.getStatsSince(MONTHLY_BUCKET, time, mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            return null;
-        }
-    }
-
-    public UsageStats[] getYearlyStatsSince(long time) {
-        try {
-            return mService.getStatsSince(YEARLY_BUCKET, time, mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            return null;
-        }
-    }
-
-    public UsageStats getRecentStatsSince(long time) {
-        UsageStats aggregatedStats = null;
-        long lastTime = time;
-        UsageStats[] stats;
-        while (true) {
-            stats = getDailyStatsSince(lastTime);
-            if (stats == null || stats.length == 0) {
-                break;
+            ParceledListSlice<UsageStats> slice = mService.queryUsageStats(intervalType, beginTime,
+                    endTime, mContext.getOpPackageName());
+            if (slice != null) {
+                return slice.getList();
             }
+        } catch (RemoteException e) {
+            // fallthrough and return null.
+        }
+        return Collections.EMPTY_LIST;
+    }
 
-            for (UsageStats stat : stats) {
-                lastTime = stat.mEndTimeStamp;
+    /**
+     * Query for events in the given time range. Events are only kept by the system for a few
+     * days.
+     * <p />
+     * <b>NOTE:</b> The last few minutes of the event log will be truncated to prevent abuse
+     * by applications.
+     *
+     * @param beginTime The inclusive beginning of the range of events to include in the results.
+     * @param endTime The exclusive end of the range of events to include in the results.
+     * @return A {@link UsageEvents}.
+     */
+    @SuppressWarnings("unchecked")
+    public UsageEvents queryEvents(long beginTime, long endTime) {
+        try {
+            UsageEvents iter = mService.queryEvents(beginTime, endTime,
+                    mContext.getOpPackageName());
+            if (iter != null) {
+                return iter;
+            }
+        } catch (RemoteException e) {
+            // fallthrough and return null
+        }
+        return sEmptyResults;
+    }
 
-                if (aggregatedStats == null) {
-                    aggregatedStats = new UsageStats();
-                    aggregatedStats.mBeginTimeStamp = stat.mBeginTimeStamp;
-                }
+    /**
+     * A convenience method that queries for all stats in the given range (using the best interval
+     * for that range), merges the resulting data, and keys it by package name.
+     * See {@link #queryUsageStats(int, long, long)}.
+     *
+     * @param beginTime The inclusive beginning of the range of stats to include in the results.
+     * @param endTime The exclusive end of the range of stats to include in the results.
+     * @return An {@link android.util.ArrayMap} keyed by package name or null if no stats are
+     *         available.
+     */
+    public ArrayMap<String, UsageStats> queryAndAggregateUsageStats(long beginTime, long endTime) {
+        List<UsageStats> stats = queryUsageStats(INTERVAL_BEST, beginTime, endTime);
+        if (stats.isEmpty()) {
+            @SuppressWarnings("unchecked")
+            ArrayMap<String, UsageStats> emptyStats = ArrayMap.EMPTY;
+            return emptyStats;
+        }
 
-                aggregatedStats.mEndTimeStamp = stat.mEndTimeStamp;
-
-                final int pkgCount = stat.getPackageCount();
-                for (int i = 0; i < pkgCount; i++) {
-                    final PackageUsageStats pkgStats = stat.getPackage(i);
-                    final PackageUsageStats aggPkgStats =
-                            aggregatedStats.getOrCreatePackageUsageStats(pkgStats.mPackageName);
-                    aggPkgStats.mTotalTimeSpent += pkgStats.mTotalTimeSpent;
-                    aggPkgStats.mLastTimeUsed = pkgStats.mLastTimeUsed;
-                    aggPkgStats.mLastEvent = pkgStats.mLastEvent;
-                }
+        ArrayMap<String, UsageStats> aggregatedStats = new ArrayMap<>();
+        final int statCount = stats.size();
+        for (int i = 0; i < statCount; i++) {
+            UsageStats newStat = stats.get(i);
+            UsageStats existingStat = aggregatedStats.get(newStat.getPackageName());
+            if (existingStat == null) {
+                aggregatedStats.put(newStat.mPackageName, newStat);
+            } else {
+                existingStat.add(newStat);
             }
         }
         return aggregatedStats;
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index 0d41be2..119d705 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -32,7 +32,7 @@
      * @param userId The user id to which the component belongs to.
      * @param timeStamp The time at which this event ocurred.
      * @param eventType The event that occured. Valid values can be found at
-     * {@link android.app.usage.UsageStats.Event}
+     * {@link UsageEvents}
      */
     public abstract void reportEvent(ComponentName component, int userId,
             long timeStamp, int eventType);
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index f0e7215..f156a08 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -17,10 +17,10 @@
 package com.android.internal.app;
 
 import android.app.Activity;
-import android.app.usage.PackageUsageStats;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
 import android.os.AsyncTask;
+import android.util.ArrayMap;
 import android.widget.AbsListView;
 import android.widget.GridView;
 import com.android.internal.R;
@@ -95,7 +95,7 @@
     private boolean mResolvingHome = false;
 
     private UsageStatsManager mUsm;
-    private UsageStats mStats;
+    private ArrayMap<String, UsageStats> mStats;
     private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
 
     private boolean mRegistered;
@@ -205,7 +205,7 @@
         mUsm = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
 
         final long sinceTime = System.currentTimeMillis() - USAGE_STATS_PERIOD;
-        mStats = mUsm.getRecentStatsSince(sinceTime);
+        mStats = mUsm.queryAndAggregateUsageStats(sinceTime, System.currentTimeMillis());
         Log.d(TAG, "sinceTime=" + sinceTime);
 
         mMaxColumns = getResources().getInteger(R.integer.config_maxResolverActivityColumns);
@@ -1018,9 +1018,6 @@
             if (lhs.targetUserId != UserHandle.USER_CURRENT) {
                 return 1;
             }
-            if (lhs.targetUserId != UserHandle.USER_CURRENT) {
-                return -1;
-            }
 
             if (mStats != null) {
                 final long timeDiff =
@@ -1042,9 +1039,9 @@
 
         private long getPackageTimeSpent(String packageName) {
             if (mStats != null) {
-                final PackageUsageStats stats = mStats.getPackage(packageName);
+                final UsageStats stats = mStats.get(packageName);
                 if (stats != null) {
-                    return stats.getTotalTimeSpent();
+                    return stats.getTotalTimeInForeground();
                 }
 
             }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 275185a..7bd2e4f 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -37,7 +37,7 @@
 import android.app.IActivityContainerCallback;
 import android.app.IAppTask;
 import android.app.admin.DevicePolicyManager;
-import android.app.usage.UsageStats;
+import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
 import android.appwidget.AppWidgetManager;
 import android.graphics.Rect;
@@ -3161,7 +3161,7 @@
             if (mUsageStatsService != null) {
                 mUsageStatsService.reportEvent(component.realActivity, component.userId,
                         System.currentTimeMillis(),
-                        UsageStats.Event.MOVE_TO_FOREGROUND);
+                        UsageEvents.Event.MOVE_TO_FOREGROUND);
             }
             synchronized (stats) {
                 stats.noteActivityResumedLocked(component.app.uid);
@@ -3170,7 +3170,7 @@
             if (mUsageStatsService != null) {
                 mUsageStatsService.reportEvent(component.realActivity, component.userId,
                         System.currentTimeMillis(),
-                        UsageStats.Event.MOVE_TO_BACKGROUND);
+                        UsageEvents.Event.MOVE_TO_BACKGROUND);
             }
             synchronized (stats) {
                 stats.noteActivityPausedLocked(component.app.uid);
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java
new file mode 100644
index 0000000..43027ad
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/IntervalStats.java
@@ -0,0 +1,81 @@
+/**
+ * Copyright (C) 2014 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.TimeSparseArray;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStats;
+import android.content.ComponentName;
+import android.util.ArrayMap;
+
+class IntervalStats {
+    public long beginTime;
+    public long endTime;
+    public long lastTimeSaved;
+    public final ArrayMap<String, UsageStats> stats = new ArrayMap<>();
+    public TimeSparseArray<UsageEvents.Event> events;
+
+    // Maps flattened string representations of component names to ComponentName.
+    // This helps save memory from using many duplicate ComponentNames and
+    // parse time when reading XML.
+    private final ArrayMap<String, ComponentName> mComponentNames = new ArrayMap<>();
+
+    UsageStats getOrCreateUsageStats(String packageName) {
+        UsageStats usageStats = stats.get(packageName);
+        if (usageStats == null) {
+            usageStats = new UsageStats();
+            usageStats.mPackageName = packageName;
+            usageStats.mBeginTimeStamp = beginTime;
+            usageStats.mEndTimeStamp = endTime;
+            stats.put(packageName, usageStats);
+        }
+        return usageStats;
+    }
+
+    void update(String packageName, long timeStamp, int eventType) {
+        UsageStats usageStats = getOrCreateUsageStats(packageName);
+
+        // TODO(adamlesinski): Ensure that we recover from incorrect event sequences
+        // like double MOVE_TO_BACKGROUND, etc.
+        if (eventType == UsageEvents.Event.MOVE_TO_BACKGROUND ||
+                eventType == UsageEvents.Event.END_OF_DAY) {
+            if (usageStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
+                    usageStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
+                usageStats.mTotalTimeInForeground += timeStamp - usageStats.mLastTimeUsed;
+            }
+        }
+        usageStats.mLastEvent = eventType;
+        usageStats.mLastTimeUsed = timeStamp;
+        usageStats.mEndTimeStamp = timeStamp;
+        endTime = timeStamp;
+    }
+
+    /**
+     * Return a ComponentName for the given string representation. This will use a cached
+     * copy of the ComponentName if possible, otherwise it will parse and add it to the
+     * internal cache.
+     */
+    ComponentName getCachedComponentName(String str) {
+        ComponentName name = mComponentNames.get(str);
+        if (name == null) {
+            name = ComponentName.unflattenFromString(str);
+            if (name != null) {
+                mComponentNames.put(str, name);
+            }
+        }
+        return name;
+    }
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index 4e75f61..e6ce0fe 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -27,30 +27,37 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.List;
 
+/**
+ * Provides an interface to query for UsageStat data from an XML database.
+ */
 class UsageStatsDatabase {
     private static final String TAG = "UsageStatsDatabase";
     private static final boolean DEBUG = UsageStatsService.DEBUG;
 
     private final Object mLock = new Object();
-    private final File[] mBucketDirs;
+    private final File[] mIntervalDirs;
     private final TimeSparseArray<AtomicFile>[] mSortedStatFiles;
     private final Calendar mCal;
 
     public UsageStatsDatabase(File dir) {
-        mBucketDirs = new File[] {
+        mIntervalDirs = new File[] {
                 new File(dir, "daily"),
                 new File(dir, "weekly"),
                 new File(dir, "monthly"),
                 new File(dir, "yearly"),
         };
-        mSortedStatFiles = new TimeSparseArray[mBucketDirs.length];
+        mSortedStatFiles = new TimeSparseArray[mIntervalDirs.length];
         mCal = Calendar.getInstance();
     }
 
+    /**
+     * Initialize any directories required and index what stats are available.
+     */
     void init() {
         synchronized (mLock) {
-            for (File f : mBucketDirs) {
+            for (File f : mIntervalDirs) {
                 f.mkdirs();
                 if (!f.exists()) {
                     throw new IllegalStateException("Failed to create directory "
@@ -68,10 +75,10 @@
             // Index the available usage stat files on disk.
             for (int i = 0; i < mSortedStatFiles.length; i++) {
                 mSortedStatFiles[i] = new TimeSparseArray<>();
-                File[] files = mBucketDirs[i].listFiles(backupFileFilter);
+                File[] files = mIntervalDirs[i].listFiles(backupFileFilter);
                 if (files != null) {
                     if (DEBUG) {
-                        Slog.d(TAG, "Found " + files.length + " stat files for bucket " + i);
+                        Slog.d(TAG, "Found " + files.length + " stat files for interval " + i);
                     }
 
                     for (File f : files) {
@@ -82,21 +89,24 @@
         }
     }
 
-    public UsageStats getLatestUsageStats(int bucketType) {
+    /**
+     * Get the latest stats that exist for this interval type.
+     */
+    public IntervalStats getLatestUsageStats(int intervalType) {
         synchronized (mLock) {
-            if (bucketType < 0 || bucketType >= mBucketDirs.length) {
-                throw new IllegalArgumentException("Bad bucket type " + bucketType);
+            if (intervalType < 0 || intervalType >= mIntervalDirs.length) {
+                throw new IllegalArgumentException("Bad interval type " + intervalType);
             }
 
-            final int fileCount = mSortedStatFiles[bucketType].size();
+            final int fileCount = mSortedStatFiles[intervalType].size();
             if (fileCount == 0) {
                 return null;
             }
 
             try {
-                final AtomicFile f = mSortedStatFiles[bucketType].valueAt(fileCount - 1);
-                UsageStats stats = UsageStatsXml.read(f);
-                stats.mLastTimeSaved = f.getLastModifiedTime();
+                final AtomicFile f = mSortedStatFiles[intervalType].valueAt(fileCount - 1);
+                IntervalStats stats = new IntervalStats();
+                UsageStatsXml.read(f, stats);
                 return stats;
             } catch (IOException e) {
                 Slog.e(TAG, "Failed to read usage stats file", e);
@@ -105,62 +115,114 @@
         return null;
     }
 
-    public UsageStats[] getUsageStats(int bucketType, long beginTime, int limit) {
+    /**
+     * Get the time at which the latest stats begin for this interval type.
+     */
+    public long getLatestUsageStatsBeginTime(int intervalType) {
         synchronized (mLock) {
-            if (bucketType < 0 || bucketType >= mBucketDirs.length) {
-                throw new IllegalArgumentException("Bad bucket type " + bucketType);
+            if (intervalType < 0 || intervalType >= mIntervalDirs.length) {
+                throw new IllegalArgumentException("Bad interval type " + intervalType);
             }
 
-            if (limit <= 0) {
-                return UsageStats.EMPTY_STATS;
+            final int statsFileCount = mSortedStatFiles[intervalType].size();
+            if (statsFileCount > 0) {
+                return mSortedStatFiles[intervalType].keyAt(statsFileCount - 1);
+            }
+            return -1;
+        }
+    }
+
+    /**
+     * Find all {@link UsageStats} for the given range and interval type.
+     */
+    public List<UsageStats> queryUsageStats(int intervalType, long beginTime, long endTime) {
+        synchronized (mLock) {
+            if (intervalType < 0 || intervalType >= mIntervalDirs.length) {
+                throw new IllegalArgumentException("Bad interval type " + intervalType);
             }
 
-            int startIndex = mSortedStatFiles[bucketType].closestIndexAfter(beginTime);
+            if (endTime < beginTime) {
+                return null;
+            }
+
+            final int startIndex = mSortedStatFiles[intervalType].closestIndexOnOrBefore(beginTime);
             if (startIndex < 0) {
-                return UsageStats.EMPTY_STATS;
+                return null;
             }
 
-            final int realLimit = Math.min(limit, mSortedStatFiles[bucketType].size() - startIndex);
+            int endIndex = mSortedStatFiles[intervalType].closestIndexOnOrAfter(endTime);
+            if (endIndex < 0) {
+                endIndex = mSortedStatFiles[intervalType].size() - 1;
+            }
+
             try {
-                ArrayList<UsageStats> stats = new ArrayList<>(realLimit);
-                for (int i = 0; i < realLimit; i++) {
-                    final AtomicFile f = mSortedStatFiles[bucketType].valueAt(startIndex + i);
+                IntervalStats stats = new IntervalStats();
+                ArrayList<UsageStats> results = new ArrayList<>();
+                for (int i = startIndex; i <= endIndex; i++) {
+                    final AtomicFile f = mSortedStatFiles[intervalType].valueAt(i);
 
                     if (DEBUG) {
                         Slog.d(TAG, "Reading stat file " + f.getBaseFile().getAbsolutePath());
                     }
 
-                    UsageStats stat = UsageStatsXml.read(f);
-                    if (beginTime < stat.mEndTimeStamp) {
-                        stat.mLastTimeSaved = f.getLastModifiedTime();
-                        stats.add(stat);
+                    UsageStatsXml.read(f, stats);
+                    if (beginTime < stats.endTime) {
+                        results.addAll(stats.stats.values());
                     }
                 }
-                return stats.toArray(new UsageStats[stats.size()]);
+                return results;
             } catch (IOException e) {
                 Slog.e(TAG, "Failed to read usage stats file", e);
-                return UsageStats.EMPTY_STATS;
+                return null;
             }
         }
     }
 
+    /**
+     * Find the interval that best matches this range.
+     *
+     * TODO(adamlesinski): Use endTimeStamp in best fit calculation.
+     */
+    public int findBestFitBucket(long beginTimeStamp, long endTimeStamp) {
+        synchronized (mLock) {
+            int bestBucket = -1;
+            long smallestDiff = Long.MAX_VALUE;
+            for (int i = mSortedStatFiles.length - 1; i >= 0; i--) {
+                final int index = mSortedStatFiles[i].closestIndexOnOrBefore(beginTimeStamp);
+                int size = mSortedStatFiles[i].size();
+                if (index >= 0 && index < size) {
+                    // We have some results here, check if they are better than our current match.
+                    long diff = Math.abs(mSortedStatFiles[i].keyAt(index) - beginTimeStamp);
+                    if (diff < smallestDiff) {
+                        smallestDiff = diff;
+                        bestBucket = i;
+                    }
+                }
+            }
+            return bestBucket;
+        }
+    }
+
+    /**
+     * Remove any usage stat files that are too old.
+     */
     public void prune() {
         synchronized (mLock) {
             long timeNow = System.currentTimeMillis();
 
             mCal.setTimeInMillis(timeNow);
             mCal.add(Calendar.MONTH, -6);
-            pruneFilesOlderThan(mBucketDirs[UsageStatsManager.MONTHLY_BUCKET],
+            pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_MONTHLY],
                     mCal.getTimeInMillis());
 
             mCal.setTimeInMillis(timeNow);
             mCal.add(Calendar.WEEK_OF_YEAR, -4);
-            pruneFilesOlderThan(mBucketDirs[UsageStatsManager.WEEKLY_BUCKET],
+            pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_WEEKLY],
                     mCal.getTimeInMillis());
 
             mCal.setTimeInMillis(timeNow);
             mCal.add(Calendar.DAY_OF_YEAR, -7);
-            pruneFilesOlderThan(mBucketDirs[UsageStatsManager.DAILY_BUCKET],
+            pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_DAILY],
                     mCal.getTimeInMillis());
         }
     }
@@ -177,23 +239,24 @@
         }
     }
 
-    public void putUsageStats(int bucketType, UsageStats stats)
-            throws IOException {
+    /**
+     * Update the stats in the database. They may not be written to disk immediately.
+     */
+    public void putUsageStats(int intervalType, IntervalStats stats) throws IOException {
         synchronized (mLock) {
-            if (bucketType < 0 || bucketType >= mBucketDirs.length) {
-                throw new IllegalArgumentException("Bad bucket type " + bucketType);
+            if (intervalType < 0 || intervalType >= mIntervalDirs.length) {
+                throw new IllegalArgumentException("Bad interval type " + intervalType);
             }
 
-            AtomicFile f = mSortedStatFiles[bucketType].get(stats.mBeginTimeStamp);
+            AtomicFile f = mSortedStatFiles[intervalType].get(stats.beginTime);
             if (f == null) {
-                f = new AtomicFile(new File(mBucketDirs[bucketType],
-                        Long.toString(stats.mBeginTimeStamp)));
-                mSortedStatFiles[bucketType].append(stats.mBeginTimeStamp, f);
+                f = new AtomicFile(new File(mIntervalDirs[intervalType],
+                        Long.toString(stats.beginTime)));
+                mSortedStatFiles[intervalType].put(stats.beginTime, f);
             }
 
-            UsageStatsXml.write(stats, f);
-            stats.mLastTimeSaved = f.getLastModifiedTime();
+            UsageStatsXml.write(f, stats);
+            stats.lastTimeSaved = f.getLastModifiedTime();
         }
     }
-
 }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 1c20d5d..c38391a 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -19,8 +19,8 @@
 import android.Manifest;
 import android.app.AppOpsManager;
 import android.app.usage.IUsageStatsManager;
+import android.app.usage.UsageEvents;
 import android.app.usage.UsageStats;
-import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -28,6 +28,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
 import android.os.Binder;
 import android.os.Environment;
@@ -47,6 +48,10 @@
 import java.util.Arrays;
 import java.util.List;
 
+/**
+ * A service that collects, aggregates, and persists application usage data.
+ * This data can be queried by apps that have been granted permission by AppOps.
+ */
 public class UsageStatsService extends SystemService implements
         UserUsageStatsService.StatsUpdatedListener {
     static final String TAG = "UsageStatsService";
@@ -54,8 +59,9 @@
     static final boolean DEBUG = false;
     private static final long TEN_SECONDS = 10 * 1000;
     private static final long TWENTY_MINUTES = 20 * 60 * 1000;
+    private static final long TWO_MINUTES = 2 * 60 * 1000;
     private static final long FLUSH_INTERVAL = DEBUG ? TEN_SECONDS : TWENTY_MINUTES;
-    static final int USAGE_STAT_RESULT_LIMIT = 10;
+    private static final long END_TIME_DELAY = DEBUG ? 0 : TWO_MINUTES;
 
     // Handler message types.
     static final int MSG_REPORT_EVENT = 0;
@@ -181,7 +187,7 @@
     /**
      * Called by the Binder stub.
      */
-    void reportEvent(UsageStats.Event event, int userId) {
+    void reportEvent(UsageEvents.Event event, int userId) {
         synchronized (mLock) {
             final UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId);
             service.reportEvent(event);
@@ -211,27 +217,37 @@
     /**
      * Called by the Binder stub.
      */
-    UsageStats[] getUsageStats(int userId, int bucketType, long beginTime) {
-        if (bucketType < 0 || bucketType >= UsageStatsManager.BUCKET_COUNT) {
-            return UsageStats.EMPTY_STATS;
-        }
-
+    List<UsageStats> queryUsageStats(int userId, int bucketType, long beginTime, long endTime) {
         final long timeNow = System.currentTimeMillis();
         if (beginTime > timeNow) {
-            return UsageStats.EMPTY_STATS;
+            return null;
         }
 
         synchronized (mLock) {
             UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId);
-            return service.getUsageStats(bucketType, beginTime);
+            return service.queryUsageStats(bucketType, beginTime, endTime);
         }
     }
 
     /**
      * Called by the Binder stub.
      */
-    UsageStats.Event[] getEvents(int userId, long time) {
-        return UsageStats.Event.EMPTY_EVENTS;
+    UsageEvents queryEvents(int userId, long beginTime, long endTime) {
+        final long timeNow = System.currentTimeMillis();
+
+        // Adjust the endTime so that we don't query for the latest events.
+        // This is to prevent apps from making decision based on what app launched them,
+        // etc.
+        endTime = Math.min(endTime, timeNow - END_TIME_DELAY);
+
+        if (beginTime > endTime) {
+            return null;
+        }
+
+        synchronized (mLock) {
+            UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId);
+            return service.queryEvents(beginTime, endTime);
+        }
     }
 
     private void flushToDiskLocked() {
@@ -253,7 +269,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_REPORT_EVENT:
-                    reportEvent((UsageStats.Event) msg.obj, msg.arg1);
+                    reportEvent((UsageEvents.Event) msg.obj, msg.arg1);
                     break;
 
                 case MSG_FLUSH_TO_DISK:
@@ -286,30 +302,32 @@
         }
 
         @Override
-        public UsageStats[] getStatsSince(int bucketType, long time, String callingPackage) {
+        public ParceledListSlice<UsageStats> queryUsageStats(int bucketType, long beginTime,
+                long endTime, String callingPackage) {
             if (!hasPermission(callingPackage)) {
-                return UsageStats.EMPTY_STATS;
+                return null;
             }
 
             final int userId = UserHandle.getCallingUserId();
             final long token = Binder.clearCallingIdentity();
             try {
-                return getUsageStats(userId, bucketType, time);
+                return new ParceledListSlice<>(UsageStatsService.this.queryUsageStats(
+                        userId, bucketType, beginTime, endTime));
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
         }
 
         @Override
-        public UsageStats.Event[] getEventsSince(long time, String callingPackage) {
+        public UsageEvents queryEvents(long beginTime, long endTime, String callingPackage) {
             if (!hasPermission(callingPackage)) {
-                return UsageStats.Event.EMPTY_EVENTS;
+                return null;
             }
 
             final int userId = UserHandle.getCallingUserId();
             final long token = Binder.clearCallingIdentity();
             try {
-                return getEvents(userId, time);
+                return UsageStatsService.this.queryEvents(userId, beginTime, endTime);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -326,8 +344,15 @@
         @Override
         public void reportEvent(ComponentName component, int userId,
                 long timeStamp, int eventType) {
-            UsageStats.Event event = new UsageStats.Event(component.getPackageName(), timeStamp,
-                    eventType);
+            if (component == null) {
+                Slog.w(TAG, "Event reported without a component name");
+                return;
+            }
+
+            UsageEvents.Event event = new UsageEvents.Event();
+            event.mComponent = component;
+            event.mTimeStamp = timeStamp;
+            event.mEventType = eventType;
             mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
         }
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsUtils.java b/services/usage/java/com/android/server/usage/UsageStatsUtils.java
index 887e016..dd5f3b9 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsUtils.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsUtils.java
@@ -28,7 +28,7 @@
 
     /**
      * Truncates the date to the given UsageStats bucket. For example, if the bucket is
-     * {@link UsageStatsManager#YEARLY_BUCKET}, the date is truncated to the 1st day of the year,
+     * {@link UsageStatsManager#INTERVAL_YEARLY}, the date is truncated to the 1st day of the year,
      * with the time set to 00:00:00.
      *
      * @param bucket The UsageStats bucket to truncate to.
@@ -41,19 +41,19 @@
         cal.set(Calendar.MILLISECOND, 0);
 
         switch (bucket) {
-            case UsageStatsManager.YEARLY_BUCKET:
+            case UsageStatsManager.INTERVAL_YEARLY:
                 cal.set(Calendar.DAY_OF_YEAR, 0);
                 break;
 
-            case UsageStatsManager.MONTHLY_BUCKET:
+            case UsageStatsManager.INTERVAL_MONTHLY:
                 cal.set(Calendar.DAY_OF_MONTH, 0);
                 break;
 
-            case UsageStatsManager.WEEKLY_BUCKET:
+            case UsageStatsManager.INTERVAL_WEEKLY:
                 cal.set(Calendar.DAY_OF_WEEK, 0);
                 break;
 
-            case UsageStatsManager.DAILY_BUCKET:
+            case UsageStatsManager.INTERVAL_DAILY:
                 break;
 
             default:
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXml.java b/services/usage/java/com/android/server/usage/UsageStatsXml.java
index 78f89d0..48881d0 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsXml.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsXml.java
@@ -16,8 +16,6 @@
 
 package com.android.server.usage;
 
-import android.app.usage.PackageUsageStats;
-import android.app.usage.UsageStats;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.Xml;
@@ -32,17 +30,19 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.net.ProtocolException;
 
 public class UsageStatsXml {
     private static final String TAG = "UsageStatsXml";
     private static final int CURRENT_VERSION = 1;
+    private static final String USAGESTATS_TAG = "usagestats";
+    private static final String VERSION_ATTR = "version";
 
-    public static UsageStats read(AtomicFile file) throws IOException {
+    public static void read(AtomicFile file, IntervalStats statsOut) throws IOException {
         try {
             FileInputStream in = file.openRead();
             try {
-                return read(in);
+                read(in, statsOut);
+                statsOut.lastTimeSaved = file.getLastModifiedTime();
             } finally {
                 try {
                     in.close();
@@ -56,17 +56,19 @@
         }
     }
 
-    private static final String USAGESTATS_TAG = "usagestats";
-    private static final String VERSION_ATTR = "version";
-    private static final String BEGIN_TIME_ATTR = "beginTime";
-    private static final String END_TIME_ATTR = "endTime";
-    private static final String PACKAGE_TAG = "package";
-    private static final String NAME_ATTR = "name";
-    private static final String TOTAL_TIME_ACTIVE_ATTR = "totalTimeActive";
-    private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive";
-    private static final String LAST_EVENT_ATTR = "lastEvent";
+    public static void write(AtomicFile file, IntervalStats stats) throws IOException {
+        FileOutputStream fos = file.startWrite();
+        try {
+            write(fos, stats);
+            file.finishWrite(fos);
+            fos = null;
+        } finally {
+            // When fos is null (successful write), this will no-op
+            file.failWrite(fos);
+        }
+    }
 
-    public static UsageStats read(InputStream in) throws IOException {
+    private static void read(InputStream in, IntervalStats statsOut) throws IOException {
         XmlPullParser parser = Xml.newPullParser();
         try {
             parser.setInput(in, "utf-8");
@@ -75,7 +77,9 @@
             try {
                 switch (Integer.parseInt(versionStr)) {
                     case 1:
-                        return loadVersion1(parser);
+                        UsageStatsXmlV1.read(parser, statsOut);
+                        break;
+
                     default:
                         Slog.e(TAG, "Unrecognized version " + versionStr);
                         throw new IOException("Unrecognized version " + versionStr);
@@ -90,70 +94,15 @@
         }
     }
 
-    private static UsageStats loadVersion1(XmlPullParser parser)
-            throws IOException, XmlPullParserException {
-        long beginTime = XmlUtils.readLongAttribute(parser, BEGIN_TIME_ATTR);
-        long endTime = XmlUtils.readLongAttribute(parser, END_TIME_ATTR);
-        UsageStats stats = UsageStats.create(beginTime, endTime);
-
-        XmlUtils.nextElement(parser);
-
-        while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
-            if (parser.getName().equals(PACKAGE_TAG)) {
-                String name = parser.getAttributeValue(null, NAME_ATTR);
-                if (name == null) {
-                    throw new ProtocolException("no " + NAME_ATTR + " attribute present");
-                }
-
-                PackageUsageStats pkgStats = stats.getOrCreatePackageUsageStats(name);
-                pkgStats.mTotalTimeSpent = XmlUtils.readLongAttribute(parser,
-                        TOTAL_TIME_ACTIVE_ATTR);
-                pkgStats.mLastTimeUsed = XmlUtils.readLongAttribute(parser, LAST_TIME_ACTIVE_ATTR);
-                pkgStats.mLastEvent = XmlUtils.readIntAttribute(parser, LAST_EVENT_ATTR);
-            }
-
-            // TODO(adamlesinski): Read in events here if there are any.
-
-            XmlUtils.skipCurrentTag(parser);
-        }
-        return stats;
-    }
-
-    public static void write(UsageStats stats, AtomicFile file) throws IOException {
-        FileOutputStream fos = file.startWrite();
-        try {
-            write(stats, fos);
-            file.finishWrite(fos);
-            fos = null;
-        } finally {
-            // When fos is null (successful write), this will no-op
-            file.failWrite(fos);
-        }
-    }
-
-    public static void write(UsageStats stats, OutputStream out) throws IOException {
+    private static void write(OutputStream out, IntervalStats stats) throws IOException {
         FastXmlSerializer xml = new FastXmlSerializer();
         xml.setOutput(out, "utf-8");
         xml.startDocument("utf-8", true);
         xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
         xml.startTag(null, USAGESTATS_TAG);
         xml.attribute(null, VERSION_ATTR, Integer.toString(CURRENT_VERSION));
-        xml.attribute(null, BEGIN_TIME_ATTR, Long.toString(stats.mBeginTimeStamp));
-        xml.attribute(null, END_TIME_ATTR, Long.toString(stats.mEndTimeStamp));
 
-        // Body of the stats
-        final int pkgCount = stats.getPackageCount();
-        for (int i = 0; i < pkgCount; i++) {
-            final PackageUsageStats pkgStats = stats.getPackage(i);
-            xml.startTag(null, PACKAGE_TAG);
-            xml.attribute(null, NAME_ATTR, pkgStats.mPackageName);
-            xml.attribute(null, TOTAL_TIME_ACTIVE_ATTR, Long.toString(pkgStats.mTotalTimeSpent));
-            xml.attribute(null, LAST_TIME_ACTIVE_ATTR, Long.toString(pkgStats.mLastTimeUsed));
-            xml.attribute(null, LAST_EVENT_ATTR, Integer.toString(pkgStats.mLastEvent));
-            xml.endTag(null, PACKAGE_TAG);
-        }
-
-        // TODO(adamlesinski): Write out events here if there are any.
+        UsageStatsXmlV1.write(xml, stats);
 
         xml.endTag(null, USAGESTATS_TAG);
         xml.endDocument();
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
new file mode 100644
index 0000000..916601b
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
@@ -0,0 +1,183 @@
+/**
+ * Copyright (C) 2014 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 com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.app.usage.TimeSparseArray;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStats;
+import android.content.ComponentName;
+
+import java.io.IOException;
+import java.net.ProtocolException;
+
+/**
+ * UsageStats reader/writer for version 1 of the XML format.
+ */
+final class UsageStatsXmlV1 {
+    private static final String BEGIN_TIME_ATTR = "beginTime";
+    private static final String END_TIME_ATTR = "endTime";
+    private static final String PACKAGE_TAG = "package";
+    private static final String NAME_ATTR = "name";
+    private static final String TOTAL_TIME_ACTIVE_ATTR = "totalTimeActive";
+    private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive";
+    private static final String LAST_EVENT_ATTR = "lastEvent";
+    private static final String EVENT_LOG_TAG = "event-log";
+    private static final String TYPE_ATTR = "type";
+    private static final String TIME_ATTR = "time";
+
+    private static UsageStats readNextUsageStats(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        if (parser.getEventType() != XmlPullParser.START_TAG) {
+            XmlUtils.nextElement(parser);
+        }
+
+        if (parser.getEventType() != XmlPullParser.START_TAG ||
+                !parser.getName().equals(PACKAGE_TAG)) {
+            return null;
+        }
+
+        final String name = parser.getAttributeValue(null, NAME_ATTR);
+        if (name == null) {
+            throw new ProtocolException("no " + NAME_ATTR + " attribute present");
+        }
+
+        UsageStats stats = new UsageStats();
+        stats.mPackageName = name;
+        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);
+        XmlUtils.skipCurrentTag(parser);
+        return stats;
+    }
+
+    private static UsageEvents.Event readNextEvent(XmlPullParser parser, IntervalStats statsOut)
+            throws XmlPullParserException, IOException {
+        if (parser.getEventType() != XmlPullParser.START_TAG) {
+            XmlUtils.nextElement(parser);
+        }
+
+        if (parser.getEventType() != XmlPullParser.START_TAG ||
+                !parser.getName().equals(EVENT_LOG_TAG)) {
+            return null;
+        }
+
+        final String componentName = XmlUtils.readStringAttribute(parser, NAME_ATTR);
+        if (componentName == null) {
+            throw new ProtocolException("no " + NAME_ATTR + " attribute present");
+        }
+
+        ComponentName component = statsOut.getCachedComponentName(componentName);
+        if (component == null) {
+            throw new ProtocolException("ComponentName " + componentName + " is invalid");
+        }
+
+        UsageEvents.Event event = new UsageEvents.Event();
+        event.mComponent = component;
+        event.mEventType = XmlUtils.readIntAttribute(parser, TYPE_ATTR);
+        event.mTimeStamp = XmlUtils.readLongAttribute(parser, TIME_ATTR);
+        XmlUtils.skipCurrentTag(parser);
+        return event;
+    }
+
+    private static void writeUsageStats(FastXmlSerializer serializer, UsageStats stats)
+            throws IOException {
+        serializer.startTag(null, PACKAGE_TAG);
+        serializer.attribute(null, NAME_ATTR, stats.mPackageName);
+        serializer.attribute(null, TOTAL_TIME_ACTIVE_ATTR,
+                Long.toString(stats.mTotalTimeInForeground));
+        serializer.attribute(null, LAST_TIME_ACTIVE_ATTR, Long.toString(stats.mLastTimeUsed));
+        serializer.attribute(null, LAST_EVENT_ATTR, Integer.toString(stats.mLastEvent));
+        serializer.endTag(null, PACKAGE_TAG);
+    }
+
+    private static void writeEvent(FastXmlSerializer serializer, UsageEvents.Event event)
+            throws IOException {
+        serializer.startTag(null, EVENT_LOG_TAG);
+        serializer.attribute(null, NAME_ATTR, event.getComponent().flattenToString());
+        serializer.attribute(null, TYPE_ATTR, Integer.toString(event.getEventType()));
+        serializer.attribute(null, TIME_ATTR, Long.toString(event.getTimeStamp()));
+        serializer.endTag(null, EVENT_LOG_TAG);
+    }
+
+    /**
+     * Reads from the {@link XmlPullParser}, assuming that it is already on the
+     * <code><usagestats></code> tag.
+     *
+     * @param parser The parser from which to read events.
+     * @param statsOut The stats object to populate with the data from the XML file.
+     */
+    public static void read(XmlPullParser parser, IntervalStats statsOut)
+            throws XmlPullParserException, IOException {
+        statsOut.stats.clear();
+
+        if (statsOut.events != null) {
+            statsOut.events.clear();
+        }
+
+        statsOut.beginTime = XmlUtils.readLongAttribute(parser, BEGIN_TIME_ATTR);
+        statsOut.endTime = XmlUtils.readLongAttribute(parser, END_TIME_ATTR);
+        XmlUtils.nextElement(parser);
+
+        UsageStats pkgStats;
+        while ((pkgStats = readNextUsageStats(parser)) != null) {
+            pkgStats.mBeginTimeStamp = statsOut.beginTime;
+            pkgStats.mEndTimeStamp = statsOut.endTime;
+            statsOut.stats.put(pkgStats.mPackageName, pkgStats);
+        }
+
+        UsageEvents.Event event;
+        while ((event = readNextEvent(parser, statsOut)) != null) {
+            if (statsOut.events == null) {
+                statsOut.events = new TimeSparseArray<>();
+            }
+            statsOut.events.put(event.getTimeStamp(), event);
+        }
+    }
+
+    /**
+     * Writes the stats object to an XML file. The {@link FastXmlSerializer}
+     * 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 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));
+
+        final int statsCount = stats.stats.size();
+        for (int i = 0; i < statsCount; i++) {
+            writeUsageStats(serializer, stats.stats.valueAt(i));
+        }
+
+        if (stats.events != null) {
+            final int eventCount = stats.events.size();
+            for (int i = 0; i < eventCount; i++) {
+                writeEvent(serializer, stats.events.valueAt(i));
+            }
+        }
+    }
+
+    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 d124188..2dfd0f6 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -1,15 +1,36 @@
+/**
+ * Copyright (C) 2014 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.PackageUsageStats;
+import android.app.usage.TimeSparseArray;
+import android.app.usage.UsageEvents;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
+import android.content.ComponentName;
 import android.util.ArraySet;
 import android.util.Slog;
 
 import java.io.File;
 import java.io.IOException;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Calendar;
+import java.util.List;
 
 /**
  * A per-user UsageStatsService. All methods are meant to be called with the main lock held
@@ -21,7 +42,7 @@
     private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 
     private final UsageStatsDatabase mDatabase;
-    private final UsageStats[] mCurrentStats = new UsageStats[UsageStatsManager.BUCKET_COUNT];
+    private final IntervalStats[] mCurrentStats;
     private boolean mStatsChanged = false;
     private final Calendar mDailyExpiryDate;
     private final StatsUpdatedListener mListener;
@@ -34,6 +55,7 @@
     UserUsageStatsService(int userId, File usageStatsDir, StatsUpdatedListener listener) {
         mDailyExpiryDate = Calendar.getInstance();
         mDatabase = new UsageStatsDatabase(usageStatsDir);
+        mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT];
         mListener = listener;
         mLogPrefix = "User[" + Integer.toString(userId) + "] ";
     }
@@ -45,6 +67,8 @@
         for (int i = 0; i < mCurrentStats.length; i++) {
             mCurrentStats[i] = mDatabase.getLatestUsageStats(i);
             if (mCurrentStats[i] == null) {
+                // Find out how many intervals we don't have data for.
+                // Ideally it should be all or none.
                 nullCount++;
             }
         }
@@ -66,85 +90,138 @@
             // This may actually be today and we will rollover on the first event
             // that is reported.
             mDailyExpiryDate.setTimeInMillis(
-                    mCurrentStats[UsageStatsManager.DAILY_BUCKET].mBeginTimeStamp);
+                    mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime);
             mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1);
-            UsageStatsUtils.truncateDateTo(UsageStatsManager.DAILY_BUCKET, mDailyExpiryDate);
+            UsageStatsUtils.truncateDateTo(UsageStatsManager.INTERVAL_DAILY, mDailyExpiryDate);
             Slog.i(TAG, mLogPrefix + "Rollover scheduled for "
                     + sDateFormat.format(mDailyExpiryDate.getTime()));
         }
 
         // Now close off any events that were open at the time this was saved.
-        for (UsageStats stat : mCurrentStats) {
-            final int pkgCount = stat.getPackageCount();
+        for (IntervalStats stat : mCurrentStats) {
+            final int pkgCount = stat.stats.size();
             for (int i = 0; i < pkgCount; i++) {
-                PackageUsageStats pkgStats = stat.getPackage(i);
-                if (pkgStats.mLastEvent == UsageStats.Event.MOVE_TO_FOREGROUND ||
-                        pkgStats.mLastEvent == UsageStats.Event.CONTINUE_PREVIOUS_DAY) {
-                    updateStats(stat, pkgStats.mPackageName, stat.mLastTimeSaved,
-                            UsageStats.Event.END_OF_DAY);
+                UsageStats pkgStats = stat.stats.valueAt(i);
+                if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
+                        pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
+                    stat.update(pkgStats.mPackageName, stat.lastTimeSaved,
+                            UsageEvents.Event.END_OF_DAY);
                     notifyStatsChanged();
                 }
             }
         }
     }
 
-    void reportEvent(UsageStats.Event event) {
+    void reportEvent(UsageEvents.Event event) {
         if (DEBUG) {
-            Slog.d(TAG, mLogPrefix + "Got usage event for " + event.packageName
-                    + "[" + event.timeStamp + "]: "
-                    + eventToString(event.eventType));
+            Slog.d(TAG, mLogPrefix + "Got usage event for " + event.getComponent().getPackageName()
+                    + "[" + event.getTimeStamp() + "]: "
+                    + eventToString(event.getEventType()));
         }
 
-        if (event.timeStamp >= mDailyExpiryDate.getTimeInMillis()) {
+        if (event.getTimeStamp() >= mDailyExpiryDate.getTimeInMillis()) {
             // Need to rollover
             rolloverStats();
         }
 
-        for (UsageStats stats : mCurrentStats) {
-            updateStats(stats, event.packageName, event.timeStamp, event.eventType);
+        if (mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events == null) {
+            mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events = new TimeSparseArray<>();
+        }
+        mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events.put(event.getTimeStamp(), event);
+
+        for (IntervalStats stats : mCurrentStats) {
+            stats.update(event.getComponent().getPackageName(), event.getTimeStamp(),
+                    event.getEventType());
         }
 
         notifyStatsChanged();
     }
 
-    UsageStats[] getUsageStats(int bucketType, long beginTime) {
-        if (beginTime >= mCurrentStats[bucketType].mEndTimeStamp) {
+    List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) {
+        if (bucketType == UsageStatsManager.INTERVAL_BEST) {
+            bucketType = mDatabase.findBestFitBucket(beginTime, endTime);
+        }
+
+        if (bucketType < 0 || bucketType >= mCurrentStats.length) {
+            if (DEBUG) {
+                Slog.d(TAG, mLogPrefix + "Bad bucketType used " + bucketType);
+            }
+            return null;
+        }
+
+        if (beginTime >= mCurrentStats[bucketType].endTime) {
             if (DEBUG) {
                 Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is "
-                        + mCurrentStats[bucketType].mEndTimeStamp);
+                        + mCurrentStats[bucketType].endTime);
             }
             // Nothing newer available.
-            return UsageStats.EMPTY_STATS;
+            return null;
 
-        } else if (beginTime >= mCurrentStats[bucketType].mBeginTimeStamp) {
+        } else if (beginTime >= mCurrentStats[bucketType].beginTime) {
             if (DEBUG) {
-                Slog.d(TAG, mLogPrefix + "Returning in-memory stats");
+                Slog.d(TAG, mLogPrefix + "Returning in-memory stats for bucket " + bucketType);
             }
             // Fast path for retrieving in-memory state.
-            // TODO(adamlesinski): This copy just to parcel the object is wasteful.
-            // It would be nice to parcel it here and send that back, but the Binder API
-            // would need to change.
-            return new UsageStats[] { new UsageStats(mCurrentStats[bucketType]) };
-
-        } else {
-            // Flush any changes that were made to disk before we do a disk query.
-            persistActiveStats();
+            ArrayList<UsageStats> results = new ArrayList<>();
+            final int packageCount = mCurrentStats[bucketType].stats.size();
+            for (int i = 0; i < packageCount; i++) {
+                results.add(new UsageStats(mCurrentStats[bucketType].stats.valueAt(i)));
+            }
+            return results;
         }
 
+        // Flush any changes that were made to disk before we do a disk query.
+        // If we're not grabbing the ongoing stats, no need to persist.
+        persistActiveStats();
+
         if (DEBUG) {
             Slog.d(TAG, mLogPrefix + "SELECT * FROM " + bucketType + " WHERE beginTime >= "
-                    + beginTime + " LIMIT " + UsageStatsService.USAGE_STAT_RESULT_LIMIT);
+                    + beginTime + " AND endTime < " + endTime);
         }
 
-        final UsageStats[] results = mDatabase.getUsageStats(bucketType, beginTime,
-                UsageStatsService.USAGE_STAT_RESULT_LIMIT);
-
+        final List<UsageStats> results = mDatabase.queryUsageStats(bucketType, beginTime, endTime);
         if (DEBUG) {
-            Slog.d(TAG, mLogPrefix + "Results: " + results.length);
+            Slog.d(TAG, mLogPrefix + "Results: " + results.size());
         }
         return results;
     }
 
+    UsageEvents queryEvents(long beginTime, long endTime) {
+        if (endTime > mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime) {
+            if (beginTime > mCurrentStats[UsageStatsManager.INTERVAL_DAILY].endTime) {
+                return null;
+            }
+
+            TimeSparseArray<UsageEvents.Event> events =
+                    mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events;
+            if (events == null) {
+                return null;
+            }
+
+            final int startIndex = events.closestIndexOnOrAfter(beginTime);
+            if (startIndex < 0) {
+                return null;
+            }
+
+            ArraySet<ComponentName> names = new ArraySet<>();
+            ArrayList<UsageEvents.Event> results = new ArrayList<>();
+            final int size = events.size();
+            for (int i = startIndex; i < size; i++) {
+                if (events.keyAt(i) >= endTime) {
+                    break;
+                }
+                names.add(events.valueAt(i).getComponent());
+                results.add(events.valueAt(i));
+            }
+            ComponentName[] table = names.toArray(new ComponentName[names.size()]);
+            Arrays.sort(table);
+            return new UsageEvents(results, table);
+        }
+
+        // TODO(adamlesinski): Query the previous days.
+        return null;
+    }
+
     void persistActiveStats() {
         if (mStatsChanged) {
             Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
@@ -166,15 +243,15 @@
         // Finish any ongoing events with an END_OF_DAY event. Make a note of which components
         // need a new CONTINUE_PREVIOUS_DAY entry.
         ArraySet<String> continuePreviousDay = new ArraySet<>();
-        for (UsageStats stat : mCurrentStats) {
-            final int pkgCount = stat.getPackageCount();
+        for (IntervalStats stat : mCurrentStats) {
+            final int pkgCount = stat.stats.size();
             for (int i = 0; i < pkgCount; i++) {
-                PackageUsageStats pkgStats = stat.getPackage(i);
-                if (pkgStats.mLastEvent == UsageStats.Event.MOVE_TO_FOREGROUND ||
-                        pkgStats.mLastEvent == UsageStats.Event.CONTINUE_PREVIOUS_DAY) {
+                UsageStats pkgStats = stat.stats.valueAt(i);
+                if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
+                        pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
                     continuePreviousDay.add(pkgStats.mPackageName);
-                    updateStats(stat, pkgStats.mPackageName,
-                            mDailyExpiryDate.getTimeInMillis() - 1, UsageStats.Event.END_OF_DAY);
+                    stat.update(pkgStats.mPackageName,
+                            mDailyExpiryDate.getTimeInMillis() - 1, UsageEvents.Event.END_OF_DAY);
                     mStatsChanged = true;
                 }
             }
@@ -187,10 +264,9 @@
         final int continueCount = continuePreviousDay.size();
         for (int i = 0; i < continueCount; i++) {
             String name = continuePreviousDay.valueAt(i);
-            for (UsageStats stat : mCurrentStats) {
-                updateStats(stat, name,
-                        mCurrentStats[UsageStatsManager.DAILY_BUCKET].mBeginTimeStamp,
-                        UsageStats.Event.CONTINUE_PREVIOUS_DAY);
+            for (IntervalStats stat : mCurrentStats) {
+                stat.update(name, mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime,
+                        UsageEvents.Event.CONTINUE_PREVIOUS_DAY);
                 mStatsChanged = true;
             }
         }
@@ -212,61 +288,67 @@
         final long timeNow = System.currentTimeMillis();
 
         Calendar tempCal = mDailyExpiryDate;
-        for (int i = 0; i < mCurrentStats.length; i++) {
+        for (int bucketType = 0; bucketType < mCurrentStats.length; bucketType++) {
             tempCal.setTimeInMillis(timeNow);
-            UsageStatsUtils.truncateDateTo(i, tempCal);
+            UsageStatsUtils.truncateDateTo(bucketType, tempCal);
 
-            if (mCurrentStats[i] != null &&
-                    mCurrentStats[i].mBeginTimeStamp == tempCal.getTimeInMillis()) {
+            if (mCurrentStats[bucketType] != null &&
+                    mCurrentStats[bucketType].beginTime == tempCal.getTimeInMillis()) {
                 // These are the same, no need to load them (in memory stats are always newer
                 // than persisted stats).
                 continue;
             }
 
-            UsageStats[] stats = mDatabase.getUsageStats(i, timeNow, 1);
-            if (stats != null && stats.length > 0) {
-                mCurrentStats[i] = stats[stats.length - 1];
+            final long lastBeginTime = mDatabase.getLatestUsageStatsBeginTime(bucketType);
+            if (lastBeginTime >= tempCal.getTimeInMillis()) {
+                if (DEBUG) {
+                    Slog.d(TAG, mLogPrefix + "Loading existing stats (" + lastBeginTime +
+                            ") for bucket " + bucketType);
+                }
+                mCurrentStats[bucketType] = mDatabase.getLatestUsageStats(bucketType);
+                if (DEBUG) {
+                    if (mCurrentStats[bucketType] != null) {
+                        Slog.d(TAG, mLogPrefix + "Found " +
+                                (mCurrentStats[bucketType].events == null ?
+                                        0 : mCurrentStats[bucketType].events.size()) +
+                                " events");
+                    }
+                }
             } else {
-                mCurrentStats[i] = UsageStats.create(tempCal.getTimeInMillis(), timeNow);
+                mCurrentStats[bucketType] = null;
+            }
+
+            if (mCurrentStats[bucketType] == null) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Creating new stats (" + tempCal.getTimeInMillis() +
+                            ") for bucket " + bucketType);
+
+                }
+                mCurrentStats[bucketType] = new IntervalStats();
+                mCurrentStats[bucketType].beginTime = tempCal.getTimeInMillis();
+                mCurrentStats[bucketType].endTime = timeNow;
             }
         }
         mStatsChanged = false;
         mDailyExpiryDate.setTimeInMillis(timeNow);
         mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1);
-        UsageStatsUtils.truncateDateTo(UsageStatsManager.DAILY_BUCKET, mDailyExpiryDate);
+        UsageStatsUtils.truncateDateTo(UsageStatsManager.INTERVAL_DAILY, mDailyExpiryDate);
         Slog.i(TAG, mLogPrefix + "Rollover scheduled for "
                 + sDateFormat.format(mDailyExpiryDate.getTime()));
     }
 
-    private void updateStats(UsageStats stats, String packageName, long timeStamp,
-            int eventType) {
-        PackageUsageStats pkgStats = stats.getOrCreatePackageUsageStats(packageName);
-
-        // TODO(adamlesinski): Ensure that we recover from incorrect event sequences
-        // like double MOVE_TO_BACKGROUND, etc.
-        if (eventType == UsageStats.Event.MOVE_TO_BACKGROUND ||
-                eventType == UsageStats.Event.END_OF_DAY) {
-            if (pkgStats.mLastEvent == UsageStats.Event.MOVE_TO_FOREGROUND ||
-                    pkgStats.mLastEvent == UsageStats.Event.CONTINUE_PREVIOUS_DAY) {
-                pkgStats.mTotalTimeSpent += timeStamp - pkgStats.mLastTimeUsed;
-            }
-        }
-        pkgStats.mLastEvent = eventType;
-        pkgStats.mLastTimeUsed = timeStamp;
-        stats.mEndTimeStamp = timeStamp;
-    }
 
     private static String eventToString(int eventType) {
         switch (eventType) {
-            case UsageStats.Event.NONE:
+            case UsageEvents.Event.NONE:
                 return "NONE";
-            case UsageStats.Event.MOVE_TO_BACKGROUND:
+            case UsageEvents.Event.MOVE_TO_BACKGROUND:
                 return "MOVE_TO_BACKGROUND";
-            case UsageStats.Event.MOVE_TO_FOREGROUND:
+            case UsageEvents.Event.MOVE_TO_FOREGROUND:
                 return "MOVE_TO_FOREGROUND";
-            case UsageStats.Event.END_OF_DAY:
+            case UsageEvents.Event.END_OF_DAY:
                 return "END_OF_DAY";
-            case UsageStats.Event.CONTINUE_PREVIOUS_DAY:
+            case UsageEvents.Event.CONTINUE_PREVIOUS_DAY:
                 return "CONTINUE_PREVIOUS_DAY";
             default:
                 return "UNKNOWN";
diff --git a/tests/UsageStatsTest/AndroidManifest.xml b/tests/UsageStatsTest/AndroidManifest.xml
index fac5810..589674a 100644
--- a/tests/UsageStatsTest/AndroidManifest.xml
+++ b/tests/UsageStatsTest/AndroidManifest.xml
@@ -13,5 +13,7 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+
+        <activity android:name=".UsageLogActivity" />
     </application>
 </manifest>
diff --git a/tests/UsageStatsTest/res/menu/main.xml b/tests/UsageStatsTest/res/menu/main.xml
new file mode 100644
index 0000000..e781058
--- /dev/null
+++ b/tests/UsageStatsTest/res/menu/main.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/log"
+        android:title="View Log"/>
+</menu>
diff --git a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java
new file mode 100644
index 0000000..5d8fc9e
--- /dev/null
+++ b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java
@@ -0,0 +1,135 @@
+/**
+ * Copyright (C) 2014 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.tests.usagestats;
+
+import android.app.ListActivity;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class UsageLogActivity extends ListActivity implements Runnable {
+    private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
+
+    private UsageStatsManager mUsageStatsManager;
+    private Adapter mAdapter;
+    private Handler mHandler = new Handler();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
+        mAdapter = new Adapter();
+        setListAdapter(mAdapter);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        run();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mHandler.removeCallbacks(this);
+    }
+
+    @Override
+    public void run() {
+        long now = System.currentTimeMillis();
+        long beginTime = now - USAGE_STATS_PERIOD;
+        UsageEvents events = mUsageStatsManager.queryEvents(beginTime, now);
+        mAdapter.update(events);
+        mHandler.postDelayed(this, 1000 * 5);
+    }
+
+    private class Adapter extends BaseAdapter {
+
+        private final ArrayList<UsageEvents.Event> mEvents = new ArrayList<>();
+
+        public void update(UsageEvents results) {
+            mEvents.clear();
+            while (results.hasNextEvent()) {
+                UsageEvents.Event event = new UsageEvents.Event();
+                results.getNextEvent(event);
+                mEvents.add(event);
+            }
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public int getCount() {
+            return mEvents.size();
+        }
+
+        @Override
+        public Object getItem(int position) {
+            return mEvents.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            final ViewHolder holder;
+            if (convertView == null) {
+                convertView = LayoutInflater.from(UsageLogActivity.this)
+                        .inflate(R.layout.row_item, parent, false);
+                holder = new ViewHolder();
+                holder.packageName = (TextView) convertView.findViewById(android.R.id.text1);
+                holder.state = (TextView) convertView.findViewById(android.R.id.text2);
+                convertView.setTag(holder);
+            } else {
+                holder = (ViewHolder) convertView.getTag();
+            }
+
+            holder.packageName.setText(mEvents.get(position).getComponent().toShortString());
+            String state;
+            switch (mEvents.get(position).getEventType()) {
+                case UsageEvents.Event.MOVE_TO_FOREGROUND:
+                    state = "Foreground";
+                    break;
+
+                case UsageEvents.Event.MOVE_TO_BACKGROUND:
+                    state = "Background";
+                    break;
+
+                default:
+                    state = "Unknown: " + mEvents.get(position).getEventType();
+                    break;
+            }
+            holder.state.setText(state);
+            return convertView;
+        }
+    }
+
+    static class ViewHolder {
+        public TextView packageName;
+        public TextView state;
+    }
+}
diff --git a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
index 73143c5..b6591bd 100644
--- a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
+++ b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
@@ -17,31 +17,34 @@
 package com.android.tests.usagestats;
 
 import android.app.ListActivity;
-import android.app.usage.PackageUsageStats;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
 import android.content.Context;
+import android.content.Intent;
 import android.os.Bundle;
 import android.text.format.DateUtils;
+import android.util.ArrayMap;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
 import android.widget.TextView;
 
 import java.util.ArrayList;
-import java.util.Calendar;
 import java.util.Collections;
 import java.util.Comparator;
 
 public class UsageStatsActivity extends ListActivity {
-
+    private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
     private UsageStatsManager mUsageStatsManager;
     private Adapter mAdapter;
-    private Comparator<PackageUsageStats> mComparator = new Comparator<PackageUsageStats>() {
+    private Comparator<UsageStats> mComparator = new Comparator<UsageStats>() {
         @Override
-        public int compare(PackageUsageStats o1, PackageUsageStats o2) {
-            return Long.compare(o2.getTotalTimeSpent(), o1.getTotalTimeSpent());
+        public int compare(UsageStats o1, UsageStats o2) {
+            return Long.compare(o2.getTotalTimeInForeground(), o1.getTotalTimeInForeground());
         }
     };
 
@@ -50,35 +53,54 @@
         super.onCreate(savedInstanceState);
         mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
         mAdapter = new Adapter();
-        updateAdapter();
         setListAdapter(mAdapter);
     }
 
     @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        MenuInflater inflater = getMenuInflater();
+        inflater.inflate(R.menu.main, menu);
+        return super.onCreateOptionsMenu(menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.log:
+                startActivity(new Intent(this, UsageLogActivity.class));
+                return true;
+
+            default:
+                return super.onOptionsItemSelected(item);
+        }
+    }
+
+    @Override
     protected void onResume() {
         super.onResume();
         updateAdapter();
     }
 
     private void updateAdapter() {
-        Calendar cal = Calendar.getInstance();
-        cal.add(Calendar.DAY_OF_YEAR, -14);
-        UsageStats stats = mUsageStatsManager.getRecentStatsSince(cal.getTimeInMillis());
+        long now = System.currentTimeMillis();
+        long beginTime = now - USAGE_STATS_PERIOD;
+        ArrayMap<String, UsageStats> stats = mUsageStatsManager.queryAndAggregateUsageStats(
+                beginTime, now);
         mAdapter.update(stats);
     }
 
     private class Adapter extends BaseAdapter {
-        private ArrayList<PackageUsageStats> mStats = new ArrayList<>();
+        private ArrayList<UsageStats> mStats = new ArrayList<>();
 
-        public void update(UsageStats stats) {
+        public void update(ArrayMap<String, UsageStats> stats) {
             mStats.clear();
             if (stats == null) {
                 return;
             }
 
-            final int packageCount = stats.getPackageCount();
+            final int packageCount = stats.size();
             for (int i = 0; i < packageCount; i++) {
-                mStats.add(stats.getPackage(i));
+                mStats.add(stats.valueAt(i));
             }
 
             Collections.sort(mStats, mComparator);
@@ -116,7 +138,7 @@
 
             holder.packageName.setText(mStats.get(position).getPackageName());
             holder.usageTime.setText(DateUtils.formatDuration(
-                    mStats.get(position).getTotalTimeSpent()));
+                    mStats.get(position).getTotalTimeInForeground()));
             return convertView;
         }
     }