diff options
author | Adam Lesinski <adamlesinski@google.com> | 2014-07-21 15:25:30 -0700 |
---|---|---|
committer | Adam Lesinski <adamlesinski@google.com> | 2014-08-08 12:08:15 -0700 |
commit | 3516800b611a79339a3c188332d13a26e9086b09 (patch) | |
tree | 4c9f8791534cc81cd9562223d2929ee8ff554bfc /core/java/android/app/usage | |
parent | 5c09e8ad5ee8e67976066366527ee58792551953 (diff) | |
download | frameworks_base-3516800b611a79339a3c188332d13a26e9086b09.zip frameworks_base-3516800b611a79339a3c188332d13a26e9086b09.tar.gz frameworks_base-3516800b611a79339a3c188332d13a26e9086b09.tar.bz2 |
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
Diffstat (limited to 'core/java/android/app/usage')
-rw-r--r-- | core/java/android/app/usage/IUsageStatsManager.aidl | 9 | ||||
-rw-r--r-- | core/java/android/app/usage/PackageUsageStats.java | 95 | ||||
-rw-r--r-- | core/java/android/app/usage/TimeSparseArray.java | 26 | ||||
-rw-r--r-- | core/java/android/app/usage/UsageEvents.aidl (renamed from core/java/android/app/usage/UsageStats.aidl) | 3 | ||||
-rw-r--r-- | core/java/android/app/usage/UsageEvents.java | 283 | ||||
-rw-r--r-- | core/java/android/app/usage/UsageStats.java | 200 | ||||
-rw-r--r-- | core/java/android/app/usage/UsageStatsManager.java | 189 | ||||
-rw-r--r-- | core/java/android/app/usage/UsageStatsManagerInternal.java | 2 |
8 files changed, 508 insertions, 299 deletions
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 @@ import android.content.ComponentName; * {@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 @@ public class TimeSparseArray<E> extends LongSparseArray<E> { * @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 @@ public class TimeSparseArray<E> extends LongSparseArray<E> { 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 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 @@ package android.app.usage; 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 @@ public final class UsageStats implements Parcelable { /** * {@hide} */ - public long mLastTimeSaved; + public long mLastTimeUsed; - private ArrayMap<String, PackageUsageStats> mPackageStats = new ArrayMap<>(); + /** + * {@hide} + */ + public long mTotalTimeInForeground; /** - * Can be null * {@hide} */ - public TimeSparseArray<Event> mEvents; + public int mLaunchCount; /** * {@hide} */ - public static UsageStats create(long beginTimeStamp, long endTimeStamp) { - UsageStats stats = new UsageStats(); - stats.mBeginTimeStamp = beginTimeStamp; - stats.mEndTimeStamp = endTimeStamp; - return stats; - } + public int mLastEvent; /** * {@hide} @@ -129,57 +67,68 @@ public final class UsageStats implements Parcelable { } 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)); - } + mLastTimeUsed = stats.mLastTimeUsed; + mTotalTimeInForeground = stats.mTotalTimeInForeground; + mLaunchCount = stats.mLaunchCount; + mLastEvent = stats.mLastEvent; + } - 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)); - } - } + 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); + /** + * Get the last time this package was used. + */ + public long getLastTimeUsed() { + return mLastTimeUsed; } - public PackageUsageStats getPackage(String packageName) { - return mPackageStats.get(packageName); + /** + * Get the total time this package spent in the foreground. + */ + public long getTotalTimeInForeground() { + return mTotalTimeInForeground; } /** - * {@hide} + * 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 PackageUsageStats getOrCreatePackageUsageStats(String packageName) { - PackageUsageStats pkgStats = mPackageStats.get(packageName); - if (pkgStats == null) { - pkgStats = new PackageUsageStats(); - pkgStats.mPackageName = packageName; - mPackageStats.put(packageName, pkgStats); + 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 @@ public final class UsageStats implements Parcelable { @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} + * An interval type that spans a day. See {@link #queryUsageStats(int, long, long)}. */ - public static final int DAILY_BUCKET = 0; + 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 WEEKLY_BUCKET = 1; + 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 MONTHLY_BUCKET = 2; + public static final int INTERVAL_MONTHLY = 2; /** - * {@hide} + * An interval type that spans a year. See {@link #queryUsageStats(int, long, long)}. */ - public static final int YEARLY_BUCKET = 3; + 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 @@ public final class UsageStatsManager { mService = service; } - public UsageStats[] getDailyStatsSince(long time) { - try { - return mService.getStatsSince(DAILY_BUCKET, time, mContext.getOpPackageName()); - } catch (RemoteException e) { - return null; - } - } - - public UsageStats[] getWeeklyStatsSince(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(WEEKLY_BUCKET, time, mContext.getOpPackageName()); + ParceledListSlice<UsageStats> slice = mService.queryUsageStats(intervalType, beginTime, + endTime, mContext.getOpPackageName()); + if (slice != null) { + return slice.getList(); + } } catch (RemoteException e) { - return null; + // fallthrough and return null. } + return Collections.EMPTY_LIST; } - public UsageStats[] getMonthlyStatsSince(long time) { + /** + * 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 { - return mService.getStatsSince(MONTHLY_BUCKET, time, mContext.getOpPackageName()); + UsageEvents iter = mService.queryEvents(beginTime, endTime, + mContext.getOpPackageName()); + if (iter != null) { + return iter; + } } catch (RemoteException e) { - return null; + // fallthrough and return null } + return sEmptyResults; } - public UsageStats[] getYearlyStatsSince(long time) { - try { - return mService.getStatsSince(YEARLY_BUCKET, time, mContext.getOpPackageName()); - } catch (RemoteException e) { - return null; + /** + * 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; } - } - - 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; - } - for (UsageStats stat : stats) { - lastTime = stat.mEndTimeStamp; - - if (aggregatedStats == null) { - aggregatedStats = new UsageStats(); - aggregatedStats.mBeginTimeStamp = stat.mBeginTimeStamp; - } - - 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 @@ public abstract class UsageStatsManagerInternal { * @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); |