summaryrefslogtreecommitdiffstats
path: root/core/java/android/app/usage
diff options
context:
space:
mode:
authorAdam Lesinski <adamlesinski@google.com>2014-07-21 15:25:30 -0700
committerAdam Lesinski <adamlesinski@google.com>2014-08-08 12:08:15 -0700
commit3516800b611a79339a3c188332d13a26e9086b09 (patch)
tree4c9f8791534cc81cd9562223d2929ee8ff554bfc /core/java/android/app/usage
parent5c09e8ad5ee8e67976066366527ee58792551953 (diff)
downloadframeworks_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.aidl9
-rw-r--r--core/java/android/app/usage/PackageUsageStats.java95
-rw-r--r--core/java/android/app/usage/TimeSparseArray.java26
-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.java283
-rw-r--r--core/java/android/app/usage/UsageStats.java200
-rw-r--r--core/java/android/app/usage/UsageStatsManager.java189
-rw-r--r--core/java/android/app/usage/UsageStatsManagerInternal.java2
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);