summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Lesinski <adamlesinski@google.com>2014-07-16 19:09:13 -0700
committerAdam Lesinski <adamlesinski@google.com>2014-07-18 15:24:20 -0700
commit0debc9aff4c0cbc28e083a948081d91b0f171319 (patch)
tree3ac4d7a9927cdd2741f65393d4e6855508ab3c26
parentd3de42cae84fadfa1befd082a2cf1bf72f9ad82a (diff)
downloadframeworks_base-0debc9aff4c0cbc28e083a948081d91b0f171319.zip
frameworks_base-0debc9aff4c0cbc28e083a948081d91b0f171319.tar.gz
frameworks_base-0debc9aff4c0cbc28e083a948081d91b0f171319.tar.bz2
First iteration of a public UsageStats API
UsageStats API that allows apps to get a list of packages that have been recently used, along with basic stats like how long they have been in the foreground and the most recent time they were running. Bug: 15165667 Change-Id: I2a2d1ff69bd0b5703ac3d9de1780df42ad90d439
-rw-r--r--Android.mk2
-rw-r--r--api/current.txt46
-rw-r--r--core/java/android/app/ActivityManager.java23
-rw-r--r--core/java/android/app/ContextImpl.java14
-rw-r--r--core/java/android/app/UsageStats.java406
-rw-r--r--core/java/android/app/UsageStatsManager.java51
-rw-r--r--core/java/android/app/usage/IUsageStatsManager.aidl30
-rw-r--r--core/java/android/app/usage/PackageUsageStats.aidl19
-rw-r--r--core/java/android/app/usage/PackageUsageStats.java95
-rw-r--r--core/java/android/app/usage/TimeSparseArray.java71
-rw-r--r--core/java/android/app/usage/UsageStats.aidl (renamed from core/java/android/app/UsageStats.aidl)4
-rw-r--r--core/java/android/app/usage/UsageStats.java240
-rw-r--r--core/java/android/app/usage/UsageStatsManager.java124
-rw-r--r--core/java/android/app/usage/UsageStatsManagerInternal.java42
-rw-r--r--core/java/android/util/AtomicFile.java14
-rw-r--r--core/java/com/android/internal/app/IUsageStats.aidl32
-rw-r--r--services/Android.mk1
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityManagerService.java29
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityRecord.java4
-rw-r--r--services/core/java/com/android/server/am/UsageStatsService.java1242
-rw-r--r--services/java/com/android/server/SystemServer.java7
-rw-r--r--services/usage/Android.mk10
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsDatabase.java199
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java437
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsUtils.java63
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsXml.java161
26 files changed, 1596 insertions, 1770 deletions
diff --git a/Android.mk b/Android.mk
index 79500ea..d4dd6fd 100644
--- a/Android.mk
+++ b/Android.mk
@@ -93,6 +93,7 @@ LOCAL_SRC_FILES += \
core/java/android/app/backup/IFullBackupRestoreObserver.aidl \
core/java/android/app/backup/IRestoreObserver.aidl \
core/java/android/app/backup/IRestoreSession.aidl \
+ core/java/android/app/usage/IUsageStatsManager.aidl \
core/java/android/bluetooth/IBluetooth.aidl \
core/java/android/bluetooth/IBluetoothA2dp.aidl \
core/java/android/bluetooth/IBluetoothA2dpSink.aidl \
@@ -251,7 +252,6 @@ LOCAL_SRC_FILES += \
core/java/com/android/internal/app/IAppOpsService.aidl \
core/java/com/android/internal/app/IBatteryStats.aidl \
core/java/com/android/internal/app/IProcessStats.aidl \
- core/java/com/android/internal/app/IUsageStats.aidl \
core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl \
core/java/com/android/internal/app/IVoiceInteractor.aidl \
core/java/com/android/internal/app/IVoiceInteractorCallback.aidl \
diff --git a/api/current.txt b/api/current.txt
index e7a3ef1..f7e45ea 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5602,6 +5602,52 @@ package android.app.job {
}
+package android.app.usage {
+
+ public final class PackageUsageStats implements android.os.Parcelable {
+ method public int describeContents();
+ method public long getLastTimeUsed();
+ method public java.lang.String getPackageName();
+ method public long getTotalTimeSpent();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ }
+
+ public final class UsageStats implements android.os.Parcelable {
+ ctor public UsageStats(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 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);
+ }
+
+}
+
package android.appwidget {
public class AppWidgetHost {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index d2540f1..d43db78 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -19,7 +19,6 @@ package android.app;
import android.os.BatteryStats;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
-import com.android.internal.app.IUsageStats;
import com.android.internal.app.ProcessStats;
import com.android.internal.os.TransferPipe;
import com.android.internal.util.FastPrintWriter;
@@ -2226,7 +2225,7 @@ public class ActivityManager {
*
* @hide
*/
- public Map<String, Integer> getAllPackageLaunchCounts() {
+ /*public Map<String, Integer> getAllPackageLaunchCounts() {
try {
IUsageStats usageStatsService = IUsageStats.Stub.asInterface(
ServiceManager.getService("usagestats"));
@@ -2250,7 +2249,7 @@ public class ActivityManager {
Log.w(TAG, "Could not query launch counts", e);
return new HashMap<String, Integer>();
}
- }
+ }*/
/** @hide */
public static int checkComponentPermission(String permission, int uid,
@@ -2352,24 +2351,6 @@ public class ActivityManager {
}
/**
- * Returns the usage statistics of each installed package.
- *
- * @hide
- */
- public UsageStats.PackageStats[] getAllPackageUsageStats() {
- try {
- IUsageStats usageStatsService = IUsageStats.Stub.asInterface(
- ServiceManager.getService("usagestats"));
- if (usageStatsService != null) {
- return usageStatsService.getAllPkgUsageStats(ActivityThread.currentPackageName());
- }
- } catch (RemoteException e) {
- Log.w(TAG, "Could not query usage stats", e);
- }
- return new UsageStats.PackageStats[0];
- }
-
- /**
* @param userid the user's id. Zero indicates the default user
* @hide
*/
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 2fd3443..8e3323b 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -16,6 +16,8 @@
package android.app;
+import android.app.usage.IUsageStatsManager;
+import android.app.usage.UsageStatsManager;
import android.os.Build;
import android.service.persistentdata.IPersistentDataBlockService;
@@ -729,7 +731,8 @@ class ContextImpl extends Context {
IBinder iBinder = ServiceManager.getService(TV_INPUT_SERVICE);
ITvInputManager service = ITvInputManager.Stub.asInterface(iBinder);
return new TvInputManager(service, UserHandle.myUserId());
- }});
+ }
+ });
registerService(TV_PARENTAL_CONTROL_SERVICE, new ServiceFetcher() {
public Object getService(ContextImpl ctx) {
@@ -743,9 +746,12 @@ class ContextImpl extends Context {
});
registerService(USAGE_STATS_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new UsageStatsManager(ctx.getOuterContext());
- }});
+ public Object createService(ContextImpl ctx) {
+ IBinder iBinder = ServiceManager.getService(USAGE_STATS_SERVICE);
+ IUsageStatsManager service = IUsageStatsManager.Stub.asInterface(iBinder);
+ return new UsageStatsManager(ctx.getOuterContext(), service);
+ }
+ });
registerService(JOB_SCHEDULER_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
diff --git a/core/java/android/app/UsageStats.java b/core/java/android/app/UsageStats.java
deleted file mode 100644
index 0aeba59..0000000
--- a/core/java/android/app/UsageStats.java
+++ /dev/null
@@ -1,406 +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;
-
-import android.content.res.Configuration;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.SystemClock;
-import android.util.ArrayMap;
-
-import java.util.Map;
-
-/**
- * Snapshot of current usage stats data.
- * @hide
- */
-public class UsageStats implements Parcelable {
- /** @hide */
- public final ArrayMap<String, PackageStats> mPackages = new ArrayMap<String, PackageStats>();
- /** @hide */
- public final ArrayMap<Configuration, ConfigurationStats> mConfigurations
- = new ArrayMap<Configuration, ConfigurationStats>();
-
- public static class PackageStats implements Parcelable {
- private final String mPackageName;
- private int mLaunchCount;
- private long mUsageTime;
- private long mResumedTime;
-
- /** @hide */
- public final ArrayMap<String, Long> componentResumeTimes;
-
- public static final Parcelable.Creator<PackageStats> CREATOR
- = new Parcelable.Creator<PackageStats>() {
- public PackageStats createFromParcel(Parcel in) {
- return new PackageStats(in);
- }
-
- public PackageStats[] newArray(int size) {
- return new PackageStats[size];
- }
- };
-
- public String toString() {
- return "PackageStats{"
- + Integer.toHexString(System.identityHashCode(this))
- + " " + mPackageName + "}";
- }
-
- /** @hide */
- public PackageStats(String pkgName) {
- mPackageName = pkgName;
- componentResumeTimes = new ArrayMap<String, Long>();
- }
-
- /** @hide */
- public PackageStats(String pkgName, int count, long time, Map<String, Long> lastResumeTimes) {
- mPackageName = pkgName;
- mLaunchCount = count;
- mUsageTime = time;
- componentResumeTimes = new ArrayMap<String, Long>();
- componentResumeTimes.putAll(lastResumeTimes);
- }
-
- /** @hide */
- public PackageStats(Parcel source) {
- mPackageName = source.readString();
- mLaunchCount = source.readInt();
- mUsageTime = source.readLong();
- final int N = source.readInt();
- componentResumeTimes = new ArrayMap<String, Long>(N);
- for (int i = 0; i < N; i++) {
- String component = source.readString();
- long lastResumeTime = source.readLong();
- componentResumeTimes.put(component, lastResumeTime);
- }
- }
-
- /** @hide */
- public PackageStats(PackageStats pStats) {
- mPackageName = pStats.mPackageName;
- mLaunchCount = pStats.mLaunchCount;
- mUsageTime = pStats.mUsageTime;
- componentResumeTimes = new ArrayMap<String, Long>(pStats.componentResumeTimes);
- }
-
- /** @hide */
- public void resume(boolean launched) {
- if (launched) {
- mLaunchCount++;
- }
- mResumedTime = SystemClock.elapsedRealtime();
- }
-
- /** @hide */
- public void pause() {
- if (mResumedTime > 0) {
- mUsageTime += SystemClock.elapsedRealtime() - mResumedTime;
- }
- mResumedTime = 0;
- }
-
- public final String getPackageName() {
- return mPackageName;
- }
-
- public final long getUsageTime(long elapsedRealtime) {
- return mUsageTime + (mResumedTime > 0 ? (elapsedRealtime- mResumedTime) : 0);
- }
-
- public final int getLaunchCount() {
- return mLaunchCount;
- }
-
- /** @hide */
- public boolean clearUsageTimes() {
- mLaunchCount = 0;
- mUsageTime = 0;
- return mResumedTime <= 0 && componentResumeTimes.isEmpty();
- }
-
- public final int describeContents() {
- return 0;
- }
-
- public final void writeToParcel(Parcel dest, int parcelableFlags) {
- writeToParcel(dest, parcelableFlags, 0);
- }
-
- final void writeToParcel(Parcel dest, int parcelableFlags, long elapsedRealtime) {
- dest.writeString(mPackageName);
- dest.writeInt(mLaunchCount);
- dest.writeLong(elapsedRealtime > 0 ? getUsageTime(elapsedRealtime) : mUsageTime);
- dest.writeInt(componentResumeTimes.size());
- for (Map.Entry<String, Long> ent : componentResumeTimes.entrySet()) {
- dest.writeString(ent.getKey());
- dest.writeLong(ent.getValue());
- }
- }
-
- /** @hide */
- public void writeExtendedToParcel(Parcel dest, int parcelableFlags) {
- }
- }
-
- public static class ConfigurationStats implements Parcelable {
- private final Configuration mConfiguration;
- private long mLastUsedTime;
- private int mUsageCount;
- private long mUsageTime;
- private long mStartedTime;
-
- public static final Parcelable.Creator<ConfigurationStats> CREATOR
- = new Parcelable.Creator<ConfigurationStats>() {
- public ConfigurationStats createFromParcel(Parcel in) {
- return new ConfigurationStats(in);
- }
-
- public ConfigurationStats[] newArray(int size) {
- return new ConfigurationStats[size];
- }
- };
-
- public String toString() {
- return "ConfigurationStats{"
- + Integer.toHexString(System.identityHashCode(this))
- + " " + mConfiguration + "}";
- }
-
- /** @hide */
- public ConfigurationStats(Configuration config) {
- mConfiguration = config;
- }
-
- /** @hide */
- public ConfigurationStats(Parcel source) {
- mConfiguration = Configuration.CREATOR.createFromParcel(source);
- mLastUsedTime = source.readLong();
- mUsageCount = source.readInt();
- mUsageTime = source.readLong();
- }
-
- /** @hide */
- public ConfigurationStats(ConfigurationStats pStats) {
- mConfiguration = pStats.mConfiguration;
- mLastUsedTime = pStats.mLastUsedTime;
- mUsageCount = pStats.mUsageCount;
- mUsageTime = pStats.mUsageTime;
- }
-
- public final Configuration getConfiguration() {
- return mConfiguration;
- }
-
- public final long getLastUsedTime() {
- return mLastUsedTime;
- }
-
- public final long getUsageTime(long elapsedRealtime) {
- return mUsageTime + (mStartedTime > 0 ? (elapsedRealtime- mStartedTime) : 0);
- }
-
- public final int getUsageCount() {
- return mUsageCount;
- }
-
- /** @hide */
- public void start() {
- mLastUsedTime = System.currentTimeMillis();
- mUsageCount++;
- mStartedTime = SystemClock.elapsedRealtime();
- }
-
- /** @hide */
- public void stop() {
- if (mStartedTime > 0) {
- mUsageTime += SystemClock.elapsedRealtime() - mStartedTime;
- }
- mStartedTime = 0;
- }
-
- /** @hide */
- public boolean clearUsageTimes() {
- mUsageCount = 0;
- mUsageTime = 0;
- return mLastUsedTime == 0 && mStartedTime <= 0;
- }
-
- public final int describeContents() {
- return 0;
- }
-
- public final void writeToParcel(Parcel dest, int parcelableFlags) {
- writeToParcel(dest, parcelableFlags, 0);
- }
-
- final void writeToParcel(Parcel dest, int parcelableFlags, long elapsedRealtime) {
- mConfiguration.writeToParcel(dest, parcelableFlags);
- dest.writeLong(mLastUsedTime);
- dest.writeInt(mUsageCount);
- dest.writeLong(elapsedRealtime > 0 ? getUsageTime(elapsedRealtime) : mUsageTime);
- }
-
- /** @hide */
- public void writeExtendedToParcel(Parcel dest, int parcelableFlags) {
- }
- }
-
- /** @hide */
- public UsageStats() {
- }
-
- /** @hide */
- public UsageStats(Parcel source, boolean extended) {
- int N = source.readInt();
- for (int i=0; i<N; i++) {
- PackageStats pkg = extended ? onNewPackageStats(source) : new PackageStats(source);
- mPackages.put(pkg.getPackageName(), pkg);
- }
- N = source.readInt();
- for (int i=0; i<N; i++) {
- ConfigurationStats config = extended ? onNewConfigurationStats(source)
- : new ConfigurationStats(source);
- mConfigurations.put(config.getConfiguration(), config);
- }
- }
-
- public int getPackageStatsCount() {
- return mPackages.size();
- }
-
- public PackageStats getPackageStatsAt(int index) {
- return mPackages.valueAt(index);
- }
-
- public PackageStats getPackageStats(String pkgName) {
- return mPackages.get(pkgName);
- }
-
- /** @hide */
- public PackageStats getOrCreatePackageStats(String pkgName) {
- PackageStats ps = mPackages.get(pkgName);
- if (ps == null) {
- ps = onNewPackageStats(pkgName);
- mPackages.put(pkgName, ps);
- }
- return ps;
- }
-
- public int getConfigurationStatsCount() {
- return mConfigurations.size();
- }
-
- public ConfigurationStats getConfigurationStatsAt(int index) {
- return mConfigurations.valueAt(index);
- }
-
- public ConfigurationStats getConfigurationStats(Configuration config) {
- return mConfigurations.get(config);
- }
-
- /** @hide */
- public ConfigurationStats getOrCreateConfigurationStats(Configuration config) {
- ConfigurationStats cs = mConfigurations.get(config);
- if (cs == null) {
- cs = onNewConfigurationStats(config);
- mConfigurations.put(config, cs);
- }
- return cs;
- }
-
- /** @hide */
- public void clearUsageTimes() {
- for (int i=mPackages.size()-1; i>=0; i--) {
- if (mPackages.valueAt(i).clearUsageTimes()) {
- mPackages.removeAt(i);
- }
- }
- for (int i=mConfigurations.size()-1; i>=0; i--) {
- if (mConfigurations.valueAt(i).clearUsageTimes()) {
- mConfigurations.removeAt(i);
- }
- }
- }
-
- /** @hide */
- public PackageStats onNewPackageStats(String pkgName) {
- return new PackageStats(pkgName);
- }
-
- /** @hide */
- public PackageStats onNewPackageStats(Parcel source) {
- return new PackageStats(source);
- }
-
- /** @hide */
- public ConfigurationStats onNewConfigurationStats(Configuration config) {
- return new ConfigurationStats(config);
- }
-
- /** @hide */
- public ConfigurationStats onNewConfigurationStats(Parcel source) {
- return new ConfigurationStats(source);
- }
-
- public int describeContents() {
- return 0;
- }
-
- public void writeToParcel(Parcel dest, int parcelableFlags) {
- writeToParcelInner(dest, parcelableFlags, false);
- }
-
- /** @hide */
- public void writeExtendedToParcel(Parcel dest, int parcelableFlags) {
- writeToParcelInner(dest, parcelableFlags, true);
- }
-
- private void writeToParcelInner(Parcel dest, int parcelableFlags, boolean extended) {
- final long elapsedRealtime = SystemClock.elapsedRealtime();
-
- int N = mPackages.size();
- dest.writeInt(N);
- for (int i=0; i<N; i++) {
- PackageStats ps = mPackages.valueAt(i);
- ps.writeToParcel(dest, parcelableFlags, elapsedRealtime);
- if (extended) {
- ps.writeExtendedToParcel(dest, parcelableFlags);
- }
- }
- N = mConfigurations.size();
- dest.writeInt(N);
- for (int i=0; i<N; i++) {
- ConfigurationStats cs = mConfigurations.valueAt(i);
- cs.writeToParcel(dest, parcelableFlags, elapsedRealtime);
- if (extended) {
- cs.writeExtendedToParcel(dest, parcelableFlags);
- }
- }
- }
-
- public static final Parcelable.Creator<UsageStats> CREATOR
- = new Parcelable.Creator<UsageStats>() {
- public UsageStats createFromParcel(Parcel in) {
- return new UsageStats(in, false);
- }
-
- public UsageStats[] newArray(int size) {
- return new UsageStats[size];
- }
- };
-}
diff --git a/core/java/android/app/UsageStatsManager.java b/core/java/android/app/UsageStatsManager.java
deleted file mode 100644
index fbf9c3b..0000000
--- a/core/java/android/app/UsageStatsManager.java
+++ /dev/null
@@ -1,51 +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;
-
-import android.content.Context;
-import android.os.ParcelableParcel;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import com.android.internal.app.IUsageStats;
-
-/**
- * Access to usage stats data.
- * @hide
- */
-public class UsageStatsManager {
- final Context mContext;
- final IUsageStats mService;
-
- /** @hide */
- public UsageStatsManager(Context context) {
- mContext = context;
- mService = IUsageStats.Stub.asInterface(ServiceManager.getService(
- Context.USAGE_STATS_SERVICE));
- }
-
- public UsageStats getCurrentStats() {
- try {
- ParcelableParcel in = mService.getCurrentStats(mContext.getOpPackageName());
- if (in != null) {
- return new UsageStats(in.getParcel(), false);
- }
- } catch (RemoteException e) {
- // About to die.
- }
- return new UsageStats();
- }
-}
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
new file mode 100644
index 0000000..0924210
--- /dev/null
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -0,0 +1,30 @@
+/**
+ * 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.app.usage.UsageStats;
+import android.content.ComponentName;
+
+/**
+ * System private API for talking with the UsageStatsManagerService.
+ *
+ * {@hide}
+ */
+interface IUsageStatsManager {
+ UsageStats[] getStatsSince(int bucketType, long time, String callingPackage);
+ UsageStats.Event[] getEventsSince(long time, String callingPackage);
+}
diff --git a/core/java/android/app/usage/PackageUsageStats.aidl b/core/java/android/app/usage/PackageUsageStats.aidl
new file mode 100644
index 0000000..5293098
--- /dev/null
+++ b/core/java/android/app/usage/PackageUsageStats.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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;
+
+parcelable PackageUsageStats;
diff --git a/core/java/android/app/usage/PackageUsageStats.java b/core/java/android/app/usage/PackageUsageStats.java
new file mode 100644
index 0000000..ba4fa21
--- /dev/null
+++ b/core/java/android/app/usage/PackageUsageStats.java
@@ -0,0 +1,95 @@
+/**
+ * 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
new file mode 100644
index 0000000..5a72d02
--- /dev/null
+++ b/core/java/android/app/usage/TimeSparseArray.java
@@ -0,0 +1,71 @@
+/**
+ * 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.util.LongSparseArray;
+
+/**
+ * An array that indexes by a long timestamp, representing milliseconds since the epoch.
+ *
+ * {@hide}
+ */
+public class TimeSparseArray<E> extends LongSparseArray<E> {
+ public TimeSparseArray() {
+ super();
+ }
+
+ public TimeSparseArray(int initialCapacity) {
+ super(initialCapacity);
+ }
+
+ /**
+ * Finds the index of the first element whose timestamp is greater 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 closestIndexAfter(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 mid = -1;
+ long key = -1;
+ while (lo <= hi) {
+ mid = (lo + hi) >>> 1;
+ key = keyAt(mid);
+
+ if (time > key) {
+ lo = mid + 1;
+ } else if (time < key) {
+ hi = mid - 1;
+ } else {
+ return mid;
+ }
+ }
+
+ if (time < key) {
+ return mid;
+ } else if (time > key && lo < size) {
+ return lo;
+ } else {
+ return -1;
+ }
+ }
+}
diff --git a/core/java/android/app/UsageStats.aidl b/core/java/android/app/usage/UsageStats.aidl
index 7dee70a..60dbd1c 100644
--- a/core/java/android/app/UsageStats.aidl
+++ b/core/java/android/app/usage/UsageStats.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.app;
+package android.app.usage;
parcelable UsageStats;
-parcelable UsageStats.PackageStats;
+parcelable UsageStats.Event;
diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java
new file mode 100644
index 0000000..35ff665
--- /dev/null
+++ b/core/java/android/app/usage/UsageStats.java
@@ -0,0 +1,240 @@
+/**
+ * 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;
+import android.util.ArrayMap;
+
+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];
+
+ /**
+ * {@hide}
+ */
+ public long mBeginTimeStamp;
+
+ /**
+ * {@hide}
+ */
+ public long mEndTimeStamp;
+
+ /**
+ * {@hide}
+ */
+ public long mLastTimeSaved;
+
+ private ArrayMap<String, PackageUsageStats> mPackageStats = new ArrayMap<>();
+
+ /**
+ * Can be null
+ * {@hide}
+ */
+ public TimeSparseArray<Event> mEvents;
+
+ /**
+ * {@hide}
+ */
+ public static UsageStats create(long beginTimeStamp, long endTimeStamp) {
+ UsageStats stats = new UsageStats();
+ stats.mBeginTimeStamp = beginTimeStamp;
+ stats.mEndTimeStamp = endTimeStamp;
+ return stats;
+ }
+
+ /**
+ * {@hide}
+ */
+ public UsageStats() {
+ }
+
+ public UsageStats(UsageStats stats) {
+ mBeginTimeStamp = stats.mBeginTimeStamp;
+ mEndTimeStamp = stats.mEndTimeStamp;
+ mLastTimeSaved = stats.mLastTimeSaved;
+
+ final int pkgCount = stats.mPackageStats.size();
+ 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));
+ }
+ }
+ }
+
+ public long getFirstTimeStamp() {
+ return mBeginTimeStamp;
+ }
+
+ 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);
+ }
+
+ /**
+ * {@hide}
+ */
+ public PackageUsageStats getOrCreatePackageUsageStats(String packageName) {
+ PackageUsageStats pkgStats = mPackageStats.get(packageName);
+ if (pkgStats == null) {
+ pkgStats = new PackageUsageStats();
+ pkgStats.mPackageName = packageName;
+ mPackageStats.put(packageName, pkgStats);
+ }
+ return pkgStats;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ 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);
+ }
+ }
+
+ public static final Creator<UsageStats> CREATOR = new Creator<UsageStats>() {
+ @Override
+ public UsageStats createFromParcel(Parcel in) {
+ UsageStats stats = new UsageStats();
+ 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);
+ }
+ }
+
+ return stats;
+ }
+
+ @Override
+ public UsageStats[] newArray(int size) {
+ return new UsageStats[size];
+ }
+ };
+}
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
new file mode 100644
index 0000000..fe02637
--- /dev/null
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -0,0 +1,124 @@
+/**
+ * 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.Context;
+import android.os.RemoteException;
+
+public final class UsageStatsManager {
+ /**
+ * {@hide}
+ */
+ public static final int DAILY_BUCKET = 0;
+
+ /**
+ * {@hide}
+ */
+ public static final int WEEKLY_BUCKET = 1;
+
+ /**
+ * {@hide}
+ */
+ public static final int MONTHLY_BUCKET = 2;
+
+ /**
+ * {@hide}
+ */
+ public static final int YEARLY_BUCKET = 3;
+
+ /**
+ * {@hide}
+ */
+ public static final int BUCKET_COUNT = 4;
+
+ private final Context mContext;
+ private final IUsageStatsManager mService;
+
+ /**
+ * {@hide}
+ */
+ public UsageStatsManager(Context context, IUsageStatsManager service) {
+ mContext = context;
+ 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) {
+ 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;
+ }
+
+ 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;
+ }
+ }
+ }
+ return aggregatedStats;
+ }
+}
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
new file mode 100644
index 0000000..d6345f3
--- /dev/null
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -0,0 +1,42 @@
+/**
+ * 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;
+
+/**
+ * UsageStatsManager local system service interface.
+ *
+ * {@hide} Only for use within the system server.
+ */
+public abstract class UsageStatsManagerInternal {
+
+ /**
+ * Reports an event to the UsageStatsManager.
+ *
+ * @param component The component for which this event ocurred.
+ * @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}
+ */
+ public abstract void reportEvent(ComponentName component, long timeStamp, int eventType);
+
+ /**
+ * Prepares the UsageStatsService for shutdown.
+ */
+ public abstract void prepareShutdown();
+}
diff --git a/core/java/android/util/AtomicFile.java b/core/java/android/util/AtomicFile.java
index 4fca570..a6466fc 100644
--- a/core/java/android/util/AtomicFile.java
+++ b/core/java/android/util/AtomicFile.java
@@ -200,6 +200,20 @@ public class AtomicFile {
}
/**
+ * Gets the last modified time of the atomic file.
+ * {@hide}
+ *
+ * @return last modified time in milliseconds since epoch.
+ * @throws IOException
+ */
+ public long getLastModifiedTime() throws IOException {
+ if (mBackupName.exists()) {
+ return mBackupName.lastModified();
+ }
+ return mBaseName.lastModified();
+ }
+
+ /**
* A convenience for {@link #openRead()} that also reads all of the
* file contents into a byte array which is returned.
*/
diff --git a/core/java/com/android/internal/app/IUsageStats.aidl b/core/java/com/android/internal/app/IUsageStats.aidl
deleted file mode 100644
index 7e7f0e1..0000000
--- a/core/java/com/android/internal/app/IUsageStats.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2008 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.internal.app;
-
-import android.app.UsageStats;
-import android.content.ComponentName;
-import android.content.res.Configuration;
-import android.os.ParcelableParcel;
-
-interface IUsageStats {
- void noteResumeComponent(in ComponentName componentName);
- void notePauseComponent(in ComponentName componentName);
- void noteLaunchTime(in ComponentName componentName, int millis);
- void noteStartConfig(in Configuration config);
- UsageStats.PackageStats getPkgUsageStats(String callingPkg, in ComponentName componentName);
- UsageStats.PackageStats[] getAllPkgUsageStats(String callingPkg);
- ParcelableParcel getCurrentStats(String callingPkg);
-}
diff --git a/services/Android.mk b/services/Android.mk
index b4de903..3c94f43 100644
--- a/services/Android.mk
+++ b/services/Android.mk
@@ -26,6 +26,7 @@ services := \
devicepolicy \
print \
restrictions \
+ usage \
usb \
voiceinteraction
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 31c1c6c..cfbadb4 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -36,6 +36,8 @@ import android.app.IActivityContainer;
import android.app.IActivityContainerCallback;
import android.app.IAppTask;
import android.app.admin.DevicePolicyManager;
+import android.app.usage.UsageStats;
+import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
import android.graphics.Rect;
import android.os.BatteryStats;
@@ -810,7 +812,7 @@ public final class ActivityManagerService extends ActivityManagerNative
/**
* Information about component usage
*/
- final UsageStatsService mUsageStatsService;
+ UsageStatsManagerInternal mUsageStatsService;
/**
* Information about and control over application operations
@@ -2026,6 +2028,10 @@ public final class ActivityManagerService extends ActivityManagerNative
mStackSupervisor.setWindowManager(wm);
}
+ public void setUsageStatsManager(UsageStatsManagerInternal usageStatsManager) {
+ mUsageStatsService = usageStatsManager;
+ }
+
public void startObservingNativeCrashes() {
final NativeCrashListener ncl = new NativeCrashListener(this);
ncl.start();
@@ -2175,7 +2181,6 @@ public final class ActivityManagerService extends ActivityManagerNative
mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));
- mUsageStatsService = new UsageStatsService(new File(systemDir, "usagestats").toString());
mAppOpsService = new AppOpsService(new File(systemDir, "appops.xml"), mHandler);
mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml"));
@@ -2247,7 +2252,6 @@ public final class ActivityManagerService extends ActivityManagerNative
mProcessCpuThread.start();
mBatteryStatsService.publish(mContext);
- mUsageStatsService.publish(mContext);
mAppOpsService.publish(mContext);
Slog.d("AppOps", "AppOpsService published");
LocalServices.addService(ActivityManagerInternal.class, new LocalService());
@@ -3073,12 +3077,18 @@ public final class ActivityManagerService extends ActivityManagerNative
if (DEBUG_SWITCH) Slog.d(TAG, "updateUsageStats: comp=" + component + "res=" + resumed);
final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
if (resumed) {
- mUsageStatsService.noteResumeComponent(component.realActivity);
+ if (mUsageStatsService != null) {
+ mUsageStatsService.reportEvent(component.realActivity, System.currentTimeMillis(),
+ UsageStats.Event.MOVE_TO_FOREGROUND);
+ }
synchronized (stats) {
stats.noteActivityResumedLocked(component.app.uid);
}
} else {
- mUsageStatsService.notePauseComponent(component.realActivity);
+ if (mUsageStatsService != null) {
+ mUsageStatsService.reportEvent(component.realActivity, System.currentTimeMillis(),
+ UsageStats.Event.MOVE_TO_BACKGROUND);
+ }
synchronized (stats) {
stats.noteActivityPausedLocked(component.app.uid);
}
@@ -8804,7 +8814,7 @@ public final class ActivityManagerService extends ActivityManagerNative
mCoreSettingsObserver = new CoreSettingsObserver(this);
- mUsageStatsService.monitorPackages();
+ //mUsageStatsService.monitorPackages();
}
/**
@@ -9026,7 +9036,9 @@ public final class ActivityManagerService extends ActivityManagerNative
}
mAppOpsService.shutdown();
- mUsageStatsService.shutdown();
+ if (mUsageStatsService != null) {
+ mUsageStatsService.prepareShutdown();
+ }
mBatteryStatsService.shutdown();
synchronized (this) {
mProcessStats.shutdownLocked();
@@ -10086,7 +10098,6 @@ public final class ActivityManagerService extends ActivityManagerNative
}
mAppOpsService.systemReady();
- mUsageStatsService.systemReady();
mSystemReady = true;
}
@@ -14986,7 +14997,7 @@ public final class ActivityManagerService extends ActivityManagerNative
newConfig.seq = mConfigurationSeq;
mConfiguration = newConfig;
Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig);
- mUsageStatsService.noteStartConfig(newConfig);
+ //mUsageStatsService.noteStartConfig(newConfig);
final Configuration configCopy = new Configuration(mConfiguration);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 6c47922..85f49ed 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -854,7 +854,7 @@ final class ActivityRecord {
Log.i(ActivityManagerService.TAG, sb.toString());
}
if (totalTime > 0) {
- service.mUsageStatsService.noteFullyDrawnTime(realActivity, (int) totalTime);
+ //service.mUsageStatsService.noteFullyDrawnTime(realActivity, (int) totalTime);
}
fullyDrawnStartTime = 0;
stack.mFullyDrawnStartTime = 0;
@@ -886,7 +886,7 @@ final class ActivityRecord {
}
mStackSupervisor.reportActivityLaunchedLocked(false, this, thisTime, totalTime);
if (totalTime > 0) {
- service.mUsageStatsService.noteLaunchTime(realActivity, (int)totalTime);
+ //service.mUsageStatsService.noteLaunchTime(realActivity, (int)totalTime);
}
displayStartTime = 0;
stack.mLaunchStartTime = 0;
diff --git a/services/core/java/com/android/server/am/UsageStatsService.java b/services/core/java/com/android/server/am/UsageStatsService.java
deleted file mode 100644
index 4a5a554..0000000
--- a/services/core/java/com/android/server/am/UsageStatsService.java
+++ /dev/null
@@ -1,1242 +0,0 @@
-/*
- * Copyright (C) 2006-2007 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.am;
-
-import android.app.AppGlobals;
-import android.app.AppOpsManager;
-import android.app.UsageStats;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.FileUtils;
-import android.os.Parcel;
-import android.os.ParcelableParcel;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.text.format.DateFormat;
-import android.util.ArrayMap;
-import android.util.AtomicFile;
-import android.util.Slog;
-import android.util.TimeUtils;
-import android.util.Xml;
-
-import com.android.internal.app.IUsageStats;
-import com.android.internal.content.PackageMonitor;
-import com.android.internal.util.FastXmlSerializer;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.TimeZone;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * This service collects the statistics associated with usage
- * of various components, like when a particular package is launched or
- * paused and aggregates events like number of time a component is launched
- * total duration of a component launch.
- */
-public final class UsageStatsService extends IUsageStats.Stub {
- public static final String SERVICE_NAME = "usagestats";
- private static final boolean localLOGV = false;
- private static final boolean REPORT_UNEXPECTED = false;
- private static final String TAG = "UsageStats";
-
- // Current on-disk Parcel version
- private static final int VERSION = 1010;
-
- private static final int CHECKIN_VERSION = 4;
-
- private static final String FILE_PREFIX = "usage-";
-
- private static final String FILE_HISTORY = FILE_PREFIX + "history.xml";
-
- private static final int FILE_WRITE_INTERVAL = (localLOGV) ? 0 : 30*60*1000; // 30m in ms
-
- private static final int MAX_NUM_FILES = 5;
-
- private static final int NUM_LAUNCH_TIME_BINS = 10;
- private static final int[] LAUNCH_TIME_BINS = {
- 250, 500, 750, 1000, 1500, 2000, 3000, 4000, 5000
- };
-
- static IUsageStats sService;
- private Context mContext;
- private AppOpsManager mAppOps;
-
- // structure used to maintain statistics since the last checkin.
- private LocalUsageStats mStats = new LocalUsageStats();
-
- // To remove last-resume time stats when a pacakge is removed.
- private PackageMonitor mPackageMonitor;
-
- // Lock to update package stats. Methods suffixed by SLOCK should invoked with
- // this lock held
- final Object mStatsLock = new Object();
- // Lock to write to file. Methods suffixed by FLOCK should invoked with
- // this lock held.
- final Object mFileLock = new Object();
- // Order of locks is mFileLock followed by mStatsLock to avoid deadlocks
- private String mLastResumedPkg;
- private String mLastResumedComp;
- private boolean mIsResumed;
- private ConfigUsageStatsExtended mCurrentConfigStats;
- private File mFile;
- private AtomicFile mHistoryFile;
- private String mFileLeaf;
- private File mDir;
-
- private final Calendar mCal // guarded by itself
- = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
-
- private final AtomicInteger mLastWriteDay = new AtomicInteger(-1);
- private final AtomicLong mLastWriteElapsedTime = new AtomicLong(0);
- private final AtomicBoolean mUnforcedDiskWriteRunning = new AtomicBoolean(false);
-
- static class LocalUsageStats extends UsageStats {
- public LocalUsageStats() {
- }
- public LocalUsageStats(Parcel in, boolean extended) {
- super(in, extended);
- }
- @Override
- public PackageStats onNewPackageStats(String pkgName) {
- return new PkgUsageStatsExtended(pkgName);
- }
- @Override
- public PackageStats onNewPackageStats(Parcel in) {
- return new PkgUsageStatsExtended(in);
- }
- @Override
- public ConfigurationStats onNewConfigurationStats(Configuration config) {
- return new ConfigUsageStatsExtended(config);
- }
- @Override
- public ConfigurationStats onNewConfigurationStats(Parcel source) {
- return new ConfigUsageStatsExtended(source);
- }
- }
-
- static class TimeStats {
- int mCount;
- final int[] mTimes = new int[NUM_LAUNCH_TIME_BINS];
-
- TimeStats() {
- }
-
- void incCount() {
- mCount++;
- }
-
- void add(int val) {
- final int[] bins = LAUNCH_TIME_BINS;
- for (int i=0; i<NUM_LAUNCH_TIME_BINS-1; i++) {
- if (val < bins[i]) {
- mTimes[i]++;
- return;
- }
- }
- mTimes[NUM_LAUNCH_TIME_BINS-1]++;
- }
-
- TimeStats(Parcel in) {
- mCount = in.readInt();
- final int[] localTimes = mTimes;
- for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) {
- localTimes[i] = in.readInt();
- }
- }
-
- void writeToParcel(Parcel out) {
- out.writeInt(mCount);
- final int[] localTimes = mTimes;
- for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) {
- out.writeInt(localTimes[i]);
- }
- }
- }
-
- static class PkgUsageStatsExtended extends UsageStats.PackageStats {
- final ArrayMap<String, TimeStats> mLaunchTimes
- = new ArrayMap<String, TimeStats>();
- final ArrayMap<String, TimeStats> mFullyDrawnTimes
- = new ArrayMap<String, TimeStats>();
-
- PkgUsageStatsExtended(String pkgName) {
- super(pkgName);
- }
-
- PkgUsageStatsExtended(Parcel in) {
- super(in);
- final int numLaunchTimeStats = in.readInt();
- if (localLOGV) Slog.v(TAG, "Reading launch times: " + numLaunchTimeStats);
- mLaunchTimes.ensureCapacity(numLaunchTimeStats);
- for (int i=0; i<numLaunchTimeStats; i++) {
- String comp = in.readString();
- if (localLOGV) Slog.v(TAG, "Component: " + comp);
- TimeStats times = new TimeStats(in);
- mLaunchTimes.put(comp, times);
- }
-
- final int numFullyDrawnTimeStats = in.readInt();
- if (localLOGV) Slog.v(TAG, "Reading fully drawn times: " + numFullyDrawnTimeStats);
- mFullyDrawnTimes.ensureCapacity(numFullyDrawnTimeStats);
- for (int i=0; i<numFullyDrawnTimeStats; i++) {
- String comp = in.readString();
- if (localLOGV) Slog.v(TAG, "Component: " + comp);
- TimeStats times = new TimeStats(in);
- mFullyDrawnTimes.put(comp, times);
- }
- }
-
- void addLaunchCount(String comp) {
- TimeStats times = mLaunchTimes.get(comp);
- if (times == null) {
- times = new TimeStats();
- mLaunchTimes.put(comp, times);
- }
- times.incCount();
- }
-
- void addLaunchTime(String comp, int millis) {
- TimeStats times = mLaunchTimes.get(comp);
- if (times == null) {
- times = new TimeStats();
- mLaunchTimes.put(comp, times);
- }
- times.add(millis);
- }
-
- void addFullyDrawnTime(String comp, int millis) {
- TimeStats times = mFullyDrawnTimes.get(comp);
- if (times == null) {
- times = new TimeStats();
- mFullyDrawnTimes.put(comp, times);
- }
- times.add(millis);
- }
-
- public void writeExtendedToParcel(Parcel out, int parcelableFlags) {
- final int numLaunchTimeStats = mLaunchTimes.size();
- out.writeInt(numLaunchTimeStats);
- for (int i=0; i<numLaunchTimeStats; i++) {
- out.writeString(mLaunchTimes.keyAt(i));
- mLaunchTimes.valueAt(i).writeToParcel(out);
- }
- final int numFullyDrawnTimeStats = mFullyDrawnTimes.size();
- out.writeInt(numFullyDrawnTimeStats);
- for (int i=0; i<numFullyDrawnTimeStats; i++) {
- out.writeString(mFullyDrawnTimes.keyAt(i));
- mFullyDrawnTimes.valueAt(i).writeToParcel(out);
- }
- }
-
- @Override
- public boolean clearUsageTimes() {
- mLaunchTimes.clear();
- mFullyDrawnTimes.clear();
- return super.clearUsageTimes();
- }
- }
-
- static class ConfigUsageStatsExtended extends UsageStats.ConfigurationStats {
- ConfigUsageStatsExtended(Configuration config) {
- super(config);
- }
-
- ConfigUsageStatsExtended(Parcel in) {
- super(in);
- }
- }
-
- UsageStatsService(String dir) {
- if (localLOGV) Slog.v(TAG, "UsageStatsService: " + dir);
- mDir = new File(dir);
- mDir.mkdir();
-
- // Remove any old /data/system/usagestats.* files from previous versions.
- File parentDir = mDir.getParentFile();
- String files[] = parentDir.list();
- if (files != null) {
- String prefix = mDir.getName() + ".";
- for (String file : files) {
- if (file.startsWith(prefix)) {
- Slog.i(TAG, "Deleting old usage file: " + file);
- (new File(parentDir, file)).delete();
- }
- }
- }
-
- // Update current stats which are binned by date
- mFileLeaf = getCurrentDateStr(FILE_PREFIX);
- mFile = new File(mDir, mFileLeaf);
- mHistoryFile = new AtomicFile(new File(mDir, FILE_HISTORY));
- readStatsFromFile();
- readHistoryStatsFromFile();
- mLastWriteElapsedTime.set(SystemClock.elapsedRealtime());
- // mCal was set by getCurrentDateStr(), want to use that same time.
- mLastWriteDay.set(mCal.get(Calendar.DAY_OF_YEAR));
- }
-
- /*
- * Utility method to convert date into string.
- */
- private String getCurrentDateStr(String prefix) {
- StringBuilder sb = new StringBuilder();
- if (prefix != null) {
- sb.append(prefix);
- }
- synchronized (mCal) {
- mCal.setTimeInMillis(System.currentTimeMillis());
- sb.append(mCal.get(Calendar.YEAR));
- int mm = mCal.get(Calendar.MONTH) - Calendar.JANUARY +1;
- if (mm < 10) {
- sb.append("0");
- }
- sb.append(mm);
- int dd = mCal.get(Calendar.DAY_OF_MONTH);
- if (dd < 10) {
- sb.append("0");
- }
- sb.append(dd);
- }
- return sb.toString();
- }
-
- private Parcel getParcelForFile(File file) throws IOException {
- FileInputStream stream = new FileInputStream(file);
- try {
- byte[] raw = readFully(stream);
- Parcel in = Parcel.obtain();
- in.unmarshall(raw, 0, raw.length);
- in.setDataPosition(0);
- return in;
- } finally {
- stream.close();
- }
- }
-
- private void readStatsFromFile() {
- File newFile = mFile;
- synchronized (mFileLock) {
- try {
- if (newFile.exists()) {
- readStatsFLOCK(newFile);
- } else {
- // Check for file limit before creating a new file
- checkFileLimitFLOCK();
- newFile.createNewFile();
- }
- } catch (IOException e) {
- Slog.w(TAG,"Error : " + e + " reading data from file:" + newFile);
- }
- }
- }
-
- private void readStatsFLOCK(File file) throws IOException {
- Parcel in = getParcelForFile(file);
- int vers = in.readInt();
- if (vers != VERSION) { // vers will be 0 if the parcel file was empty
- Slog.w(TAG, "Usage stats version of " + file + " changed from " + vers + " to "
- + VERSION + "; dropping");
- return;
- }
- LocalUsageStats stats = new LocalUsageStats(in, true);
- synchronized (mStatsLock) {
- mStats = stats;
- }
- }
-
- private void readHistoryStatsFromFile() {
- synchronized (mFileLock) {
- if (mHistoryFile.getBaseFile().exists()) {
- readHistoryStatsFLOCK();
- }
- }
- }
-
- private void readHistoryStatsFLOCK() {
- FileInputStream fis = null;
- try {
- fis = mHistoryFile.openRead();
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(fis, null);
- int eventType = parser.getEventType();
- while (eventType != XmlPullParser.START_TAG &&
- eventType != XmlPullParser.END_DOCUMENT) {
- eventType = parser.next();
- }
- if (eventType == XmlPullParser.END_DOCUMENT) {
- return;
- }
-
- String tagName = parser.getName();
- if ("usage-history".equals(tagName)) {
- String pkg = null;
- do {
- eventType = parser.next();
- if (eventType == XmlPullParser.START_TAG) {
- tagName = parser.getName();
- int depth = parser.getDepth();
- if ("pkg".equals(tagName) && depth == 2) {
- pkg = parser.getAttributeValue(null, "name");
- } else if ("comp".equals(tagName) && depth == 3 && pkg != null) {
- String comp = parser.getAttributeValue(null, "name");
- String lastResumeTimeStr = parser.getAttributeValue(null, "lrt");
- if (comp != null && lastResumeTimeStr != null) {
- try {
- long lastResumeTime = Long.parseLong(lastResumeTimeStr);
- synchronized (mStatsLock) {
- PkgUsageStatsExtended pus = (PkgUsageStatsExtended)
- mStats.getOrCreatePackageStats(pkg);
- pus.componentResumeTimes.put(comp, lastResumeTime);
- }
- } catch (NumberFormatException e) {
- }
- }
- }
- } else if (eventType == XmlPullParser.END_TAG) {
- if ("pkg".equals(parser.getName())) {
- pkg = null;
- }
- }
- } while (eventType != XmlPullParser.END_DOCUMENT);
- }
- } catch (XmlPullParserException e) {
- Slog.w(TAG,"Error reading history stats: " + e);
- } catch (IOException e) {
- Slog.w(TAG,"Error reading history stats: " + e);
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- }
- }
- }
- }
-
- private ArrayList<String> getUsageStatsFileListFLOCK() {
- // Check if there are too many files in the system and delete older files
- String fList[] = mDir.list();
- if (fList == null) {
- return null;
- }
- ArrayList<String> fileList = new ArrayList<String>();
- for (String file : fList) {
- if (!file.startsWith(FILE_PREFIX)) {
- continue;
- }
- if (file.endsWith(".bak")) {
- (new File(mDir, file)).delete();
- continue;
- }
- fileList.add(file);
- }
- return fileList;
- }
-
- private void checkFileLimitFLOCK() {
- // Get all usage stats output files
- ArrayList<String> fileList = getUsageStatsFileListFLOCK();
- if (fileList == null) {
- // Empty /data/system/usagestats/ so we don't have anything to delete
- return;
- }
- int count = fileList.size();
- if (count <= MAX_NUM_FILES) {
- return;
- }
- // Sort files
- Collections.sort(fileList);
- count -= MAX_NUM_FILES;
- // Delete older files
- for (int i = 0; i < count; i++) {
- String fileName = fileList.get(i);
- File file = new File(mDir, fileName);
- Slog.i(TAG, "Deleting usage file : " + fileName);
- file.delete();
- }
- }
-
- /**
- * Conditionally start up a disk write if it's been awhile, or the
- * day has rolled over.
- *
- * This is called indirectly from user-facing actions (when
- * 'force' is false) so it tries to be quick, without writing to
- * disk directly or acquiring heavy locks.
- *
- * @params force do an unconditional, synchronous stats flush
- * to disk on the current thread.
- * @params forceWriteHistoryStats Force writing of historical stats.
- */
- private void writeStatsToFile(final boolean force, final boolean forceWriteHistoryStats) {
- int curDay;
- synchronized (mCal) {
- mCal.setTimeInMillis(System.currentTimeMillis());
- curDay = mCal.get(Calendar.DAY_OF_YEAR);
- }
- final boolean dayChanged = curDay != mLastWriteDay.get();
-
- // Determine if the day changed... note that this will be wrong
- // if the year has changed but we are in the same day of year...
- // we can probably live with this.
- final long currElapsedTime = SystemClock.elapsedRealtime();
-
- // Fast common path, without taking the often-contentious
- // mFileLock.
- if (!force) {
- if (!dayChanged &&
- (currElapsedTime - mLastWriteElapsedTime.get()) < FILE_WRITE_INTERVAL) {
- // wait till the next update
- return;
- }
- if (mUnforcedDiskWriteRunning.compareAndSet(false, true)) {
- new Thread("UsageStatsService_DiskWriter") {
- public void run() {
- try {
- if (localLOGV) Slog.d(TAG, "Disk writer thread starting.");
- writeStatsToFile(true, false);
- } finally {
- mUnforcedDiskWriteRunning.set(false);
- if (localLOGV) Slog.d(TAG, "Disk writer thread ending.");
- }
- }
- }.start();
- }
- return;
- }
-
- Parcel out = Parcel.obtain();
- synchronized (mStatsLock) {
- out.writeInt(VERSION);
- mStats.writeExtendedToParcel(out, 0);
- if (dayChanged) {
- mStats.clearUsageTimes();
- }
- }
-
- synchronized (mFileLock) {
- // Get the most recent file
- mFileLeaf = getCurrentDateStr(FILE_PREFIX);
- // Copy current file to back up
- File backupFile = null;
- if (mFile != null && mFile.exists()) {
- backupFile = new File(mFile.getPath() + ".bak");
- if (!backupFile.exists()) {
- if (!mFile.renameTo(backupFile)) {
- Slog.w(TAG, "Failed to persist new stats");
- out.recycle();
- return;
- }
- } else {
- mFile.delete();
- }
- }
-
- try {
- // Write mStats to file
- writeStatsFLOCK(mFile, out);
- mLastWriteElapsedTime.set(currElapsedTime);
- if (dayChanged) {
- mLastWriteDay.set(curDay);
- mFile = new File(mDir, mFileLeaf);
- checkFileLimitFLOCK();
- }
-
- if (dayChanged || forceWriteHistoryStats) {
- // Write history stats daily or when forced (due to shutdown) or when debugging.
- writeHistoryStatsFLOCK();
- }
-
- // Delete the backup file
- if (backupFile != null) {
- backupFile.delete();
- }
- } catch (IOException e) {
- Slog.w(TAG, "Failed writing stats to file:" + mFile);
- if (backupFile != null) {
- mFile.delete();
- backupFile.renameTo(mFile);
- }
- }
- out.recycle();
- }
- if (localLOGV) Slog.d(TAG, "Dumped usage stats.");
- }
-
- private void writeStatsFLOCK(File file, Parcel parcel) throws IOException {
- FileOutputStream stream = new FileOutputStream(file);
- try {
- stream.write(parcel.marshall());
- stream.flush();
- } finally {
- FileUtils.sync(stream);
- stream.close();
- }
- }
-
- /** Filter out stats for any packages which aren't present anymore. */
- private void filterHistoryStats() {
- synchronized (mStatsLock) {
- IPackageManager pm = AppGlobals.getPackageManager();
- for (int i=mStats.mPackages.size()-1; i>=0; i--) {
- try {
- if (pm.getPackageUid(mStats.mPackages.valueAt(i).getPackageName(), 0) < 0) {
- mStats.mPackages.removeAt(i);
- }
- } catch (RemoteException e) {
- }
- }
- }
- }
-
- private void writeHistoryStatsFLOCK() {
- FileOutputStream fos = null;
- try {
- fos = mHistoryFile.startWrite();
- XmlSerializer out = new FastXmlSerializer();
- out.setOutput(fos, "utf-8");
- out.startDocument(null, true);
- out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
- out.startTag(null, "usage-history");
- synchronized (mStatsLock) {
- int NP = mStats.mPackages.size();
- for (int i=0; i<NP; i++) {
- UsageStats.PackageStats ps = mStats.mPackages.valueAt(i);
- out.startTag(null, "pkg");
- out.attribute(null, "name", ps.getPackageName());
- ArrayMap<String, Long> comp = ps.componentResumeTimes;
- for (int j=0; j<comp.size(); j++) {
- out.startTag(null, "comp");
- out.attribute(null, "name", comp.keyAt(j));
- out.attribute(null, "lrt", comp.valueAt(j).toString());
- out.endTag(null, "comp");
- }
- out.endTag(null, "pkg");
- }
- }
- out.endTag(null, "usage-history");
- out.endDocument();
-
- mHistoryFile.finishWrite(fos);
- } catch (IOException e) {
- Slog.w(TAG,"Error writing history stats" + e);
- if (fos != null) {
- mHistoryFile.failWrite(fos);
- }
- }
- }
-
- public void publish(Context context) {
- mContext = context;
- ServiceManager.addService(SERVICE_NAME, asBinder());
- }
-
- public void systemReady() {
- mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
- }
-
- /**
- * Start watching packages to remove stats when a package is uninstalled.
- * May only be called when the package manager is ready.
- */
- public void monitorPackages() {
- mPackageMonitor = new PackageMonitor() {
- @Override
- public void onPackageRemovedAllUsers(String packageName, int uid) {
- synchronized (mStatsLock) {
- mStats.mPackages.remove(packageName);
- }
- }
- };
- mPackageMonitor.register(mContext, null, true);
- filterHistoryStats();
- }
-
- public void shutdown() {
- if (mPackageMonitor != null) {
- mPackageMonitor.unregister();
- }
- Slog.i(TAG, "Writing usage stats before shutdown...");
- writeStatsToFile(true, true);
- }
-
- public static IUsageStats getService() {
- if (sService != null) {
- return sService;
- }
- IBinder b = ServiceManager.getService(SERVICE_NAME);
- sService = asInterface(b);
- return sService;
- }
-
- @Override
- public void noteResumeComponent(ComponentName componentName) {
- enforceCallingPermission();
- String pkgName;
- synchronized (mStatsLock) {
- if ((componentName == null) ||
- ((pkgName = componentName.getPackageName()) == null)) {
- return;
- }
-
- final boolean samePackage = pkgName.equals(mLastResumedPkg);
- if (mIsResumed) {
- if (mLastResumedPkg != null) {
- // We last resumed some other package... just pause it now
- // to recover.
- if (REPORT_UNEXPECTED) Slog.i(TAG, "Unexpected resume of " + pkgName
- + " while already resumed in " + mLastResumedPkg);
- PkgUsageStatsExtended pus = (PkgUsageStatsExtended)mStats.getPackageStats(
- mLastResumedPkg);
- if (pus != null) {
- pus.pause();
- }
- }
- }
-
- final boolean sameComp = samePackage
- && componentName.getClassName().equals(mLastResumedComp);
-
- mIsResumed = true;
- mLastResumedPkg = pkgName;
- mLastResumedComp = componentName.getClassName();
-
- if (localLOGV) Slog.i(TAG, "started component:" + pkgName);
- PkgUsageStatsExtended pus = (PkgUsageStatsExtended)
- mStats.getOrCreatePackageStats(pkgName);
- pus.resume(!samePackage);
- if (!sameComp) {
- pus.addLaunchCount(mLastResumedComp);
- }
- pus.componentResumeTimes.put(mLastResumedComp, System.currentTimeMillis());
- }
- }
-
- @Override
- public void notePauseComponent(ComponentName componentName) {
- enforceCallingPermission();
-
- synchronized (mStatsLock) {
- String pkgName;
- if ((componentName == null) ||
- ((pkgName = componentName.getPackageName()) == null)) {
- return;
- }
- if (!mIsResumed) {
- if (REPORT_UNEXPECTED) Slog.i(TAG, "Something wrong here, didn't expect "
- + pkgName + " to be paused");
- return;
- }
- mIsResumed = false;
-
- if (localLOGV) Slog.i(TAG, "paused component:"+pkgName);
-
- PkgUsageStatsExtended pus = (PkgUsageStatsExtended)mStats.getPackageStats(pkgName);
- if (pus == null) {
- // Weird some error here
- Slog.i(TAG, "No package stats for pkg:"+pkgName);
- return;
- }
- pus.pause();
- }
-
- // Persist current data to file if needed.
- writeStatsToFile(false, false);
- }
-
- @Override
- public void noteLaunchTime(ComponentName componentName, int millis) {
- enforceCallingPermission();
- String pkgName;
- if ((componentName == null) ||
- ((pkgName = componentName.getPackageName()) == null)) {
- return;
- }
-
- // Persist current data to file if needed.
- writeStatsToFile(false, false);
-
- synchronized (mStatsLock) {
- PkgUsageStatsExtended pus = (PkgUsageStatsExtended)mStats.getPackageStats(pkgName);
- if (pus != null) {
- pus.addLaunchTime(componentName.getClassName(), millis);
- }
- }
- }
-
- public void noteFullyDrawnTime(ComponentName componentName, int millis) {
- enforceCallingPermission();
- String pkgName;
- if ((componentName == null) ||
- ((pkgName = componentName.getPackageName()) == null)) {
- return;
- }
-
- // Persist current data to file if needed.
- writeStatsToFile(false, false);
-
- synchronized (mStatsLock) {
- PkgUsageStatsExtended pus = (PkgUsageStatsExtended)mStats.getPackageStats(pkgName);
- if (pus != null) {
- pus.addFullyDrawnTime(componentName.getClassName(), millis);
- }
- }
- }
-
- public void noteStartConfig(Configuration config) {
- enforceCallingPermission();
- synchronized (mStatsLock) {
- config = new Configuration(config);
- ConfigUsageStatsExtended cus = (ConfigUsageStatsExtended)
- mStats.getOrCreateConfigurationStats(config);
- if (cus != mCurrentConfigStats) {
- if (mCurrentConfigStats != null) {
- mCurrentConfigStats.stop();
- }
- cus.start();
- mCurrentConfigStats = cus;
- }
- }
- }
-
- public void enforceCallingPermission() {
- if (Binder.getCallingPid() == Process.myPid()) {
- return;
- }
- mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), null);
- }
-
- @Override
- public UsageStats.PackageStats getPkgUsageStats(String callingPkg,
- ComponentName componentName) {
- checkCallerPermission(callingPkg, "getPkgUsageStats");
- String pkgName;
- if ((componentName == null) ||
- ((pkgName = componentName.getPackageName()) == null)) {
- return null;
- }
- synchronized (mStatsLock) {
- PkgUsageStatsExtended pus = (PkgUsageStatsExtended)mStats.getPackageStats(pkgName);
- if (pus == null) {
- return null;
- }
- return new UsageStats.PackageStats(pus);
- }
- }
-
- @Override
- public UsageStats.PackageStats[] getAllPkgUsageStats(String callingPkg) {
- checkCallerPermission(callingPkg, "getAllPkgUsageStats");
- synchronized (mStatsLock) {
- int NP = mStats.mPackages.size();
- if (NP <= 0) {
- return null;
- }
- UsageStats.PackageStats retArr[] = new UsageStats.PackageStats[NP];
- for (int p=0; p<NP; p++) {
- UsageStats.PackageStats ps = mStats.mPackages.valueAt(p);
- retArr[p] = new UsageStats.PackageStats(ps);
- }
- return retArr;
- }
- }
-
- @Override
- public ParcelableParcel getCurrentStats(String callingPkg) {
- checkCallerPermission(callingPkg, "getCurrentStats");
- synchronized (mStatsLock) {
- ParcelableParcel out = new ParcelableParcel(null);
- mStats.writeToParcel(out.getParcel(), 0);
- return out;
- }
- }
-
- private void checkCallerPermission(String callingPkg, String callingOp) {
- // Because the permission for this is system-only, its use with
- // app ops is a little different: the op is disabled by default,
- // and enabling it allows apps to get access even if they don't
- // hold the permission.
- int mode = mAppOps.noteOpNoThrow(AppOpsManager.OP_GET_USAGE_STATS, Binder.getCallingUid(),
- callingPkg);
- if (mode == AppOpsManager.MODE_ALLOWED) {
- return;
- } else if (mode != AppOpsManager.MODE_IGNORED) {
- if (mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.PACKAGE_USAGE_STATS)
- == PackageManager.PERMISSION_GRANTED) {
- return;
- }
- }
-
- String msg = "Package " + callingPkg + " not allowed to call " + callingOp;
- throw new SecurityException(msg);
- }
-
- static byte[] readFully(FileInputStream stream) throws IOException {
- int pos = 0;
- int avail = stream.available();
- byte[] data = new byte[avail];
- while (true) {
- int amt = stream.read(data, pos, data.length-pos);
- if (amt <= 0) {
- return data;
- }
- pos += amt;
- avail = stream.available();
- if (avail > data.length-pos) {
- byte[] newData = new byte[pos+avail];
- System.arraycopy(data, 0, newData, 0, pos);
- data = newData;
- }
- }
- }
-
- private void collectDumpInfoFLOCK(PrintWriter pw, boolean isCompactOutput,
- boolean deleteAfterPrint, HashSet<String> packages) {
- List<String> fileList = getUsageStatsFileListFLOCK();
- if (fileList == null) {
- return;
- }
- Collections.sort(fileList);
- for (String file : fileList) {
- if (deleteAfterPrint && file.equalsIgnoreCase(mFileLeaf)) {
- // In this mode we don't print the current day's stats, since
- // they are incomplete.
- continue;
- }
- File dFile = new File(mDir, file);
- String dateStr = file.substring(FILE_PREFIX.length());
- if (dateStr.length() > 0 && (dateStr.charAt(0) <= '0' || dateStr.charAt(0) >= '9')) {
- // If the remainder does not start with a number, it is not a date,
- // so we should ignore it for purposes here.
- continue;
- }
- try {
- Parcel in = getParcelForFile(dFile);
- collectDumpInfoFromParcelFLOCK(in, pw, dateStr, isCompactOutput,
- packages);
- if (deleteAfterPrint) {
- // Delete old file after collecting info only for checkin requests
- dFile.delete();
- }
- } catch (IOException e) {
- Slog.w(TAG, "Failed with "+e+" when collecting dump info from file : "+file);
- }
- }
- }
-
- private void collectDumpInfoFromParcelFLOCK(Parcel in, PrintWriter pw,
- String date, boolean isCompactOutput, HashSet<String> packages) {
- StringBuilder sb = new StringBuilder(512);
- if (isCompactOutput) {
- sb.append("D:");
- sb.append(CHECKIN_VERSION);
- sb.append(',');
- } else {
- sb.append("Date: ");
- }
-
- sb.append(date);
-
- int vers = in.readInt();
- if (vers != VERSION) {
- sb.append(" (old data version)");
- pw.println(sb.toString());
- return;
- }
-
- final LocalUsageStats stats = new LocalUsageStats(in, true);
- final long time = SystemClock.elapsedRealtime();
-
- pw.println(sb.toString());
- int NP = stats.mPackages.size();
- for (int p=0; p<NP; p++) {
- PkgUsageStatsExtended pus = (PkgUsageStatsExtended)stats.mPackages.valueAt(p);
- sb.setLength(0);
- if (packages != null && !packages.contains(pus.getPackageName())) {
- // This package has not been requested -- don't print
- // anything for it.
- } else if (isCompactOutput) {
- sb.append("P:");
- sb.append(pus.getPackageName());
- sb.append(',');
- sb.append(pus.getLaunchCount());
- sb.append(',');
- sb.append(pus.getUsageTime(time));
- sb.append('\n');
- final int NLT = pus.mLaunchTimes.size();
- for (int i=0; i<NLT; i++) {
- sb.append("L:");
- String activity = pus.mLaunchTimes.keyAt(i);
- sb.append(activity);
- TimeStats times = pus.mLaunchTimes.valueAt(i);
- sb.append(',');
- sb.append(times.mCount);
- for (int j=0; j<NUM_LAUNCH_TIME_BINS; j++) {
- sb.append(",");
- sb.append(times.mTimes[j]);
- }
- sb.append('\n');
- }
- final int NFDT = pus.mFullyDrawnTimes.size();
- for (int i=0; i<NFDT; i++) {
- sb.append("D:");
- String activity = pus.mFullyDrawnTimes.keyAt(i);
- sb.append(activity);
- TimeStats times = pus.mFullyDrawnTimes.valueAt(i);
- for (int j=0; j<NUM_LAUNCH_TIME_BINS; j++) {
- sb.append(",");
- sb.append(times.mTimes[j]);
- }
- sb.append('\n');
- }
- final int NC = pus.componentResumeTimes.size();
- for (int c=0; c<NC; c++) {
- pw.print("R:"); pw.print(pus.componentResumeTimes.keyAt(c)); pw.print(",");
- pw.println(pus.componentResumeTimes.valueAt(c));
- }
-
- } else {
- sb.append(" ");
- sb.append(pus.getPackageName());
- if (pus.getLaunchCount() != 0 || pus.getUsageTime(time) != 0) {
- sb.append(": ");
- sb.append(pus.getLaunchCount());
- sb.append(" times, ");
- TimeUtils.formatDuration(pus.getUsageTime(time), sb);
- } else {
- sb.append(":");
- }
- sb.append('\n');
- final int NLT = pus.mLaunchTimes.size();
- for (int i=0; i<NLT; i++) {
- sb.append(" ");
- sb.append(pus.mLaunchTimes.keyAt(i));
- TimeStats times = pus.mLaunchTimes.valueAt(i);
- sb.append(": ");
- sb.append(times.mCount);
- sb.append(" starts");
- int lastBin = 0;
- for (int j=0; j<NUM_LAUNCH_TIME_BINS-1; j++) {
- if (times.mTimes[j] != 0) {
- sb.append(", ");
- sb.append(lastBin);
- sb.append('-');
- sb.append(LAUNCH_TIME_BINS[j]);
- sb.append("ms=");
- sb.append(times.mTimes[j]);
- }
- lastBin = LAUNCH_TIME_BINS[j];
- }
- if (times.mTimes[NUM_LAUNCH_TIME_BINS-1] != 0) {
- sb.append(", ");
- sb.append(">=");
- sb.append(lastBin);
- sb.append("ms=");
- sb.append(times.mTimes[NUM_LAUNCH_TIME_BINS-1]);
- }
- sb.append('\n');
- }
- final int NFDT = pus.mFullyDrawnTimes.size();
- for (int i=0; i<NFDT; i++) {
- sb.append(" ");
- sb.append(pus.mFullyDrawnTimes.keyAt(i));
- TimeStats times = pus.mFullyDrawnTimes.valueAt(i);
- sb.append(": fully drawn ");
- boolean needComma = false;
- int lastBin = 0;
- for (int j=0; j<NUM_LAUNCH_TIME_BINS-1; j++) {
- if (times.mTimes[j] != 0) {
- if (needComma) {
- sb.append(", ");
- } else {
- needComma = true;
- }
- sb.append(lastBin);
- sb.append('-');
- sb.append(LAUNCH_TIME_BINS[j]);
- sb.append("ms=");
- sb.append(times.mTimes[j]);
- }
- lastBin = LAUNCH_TIME_BINS[j];
- }
- if (times.mTimes[NUM_LAUNCH_TIME_BINS-1] != 0) {
- if (needComma) {
- sb.append(", ");
- }
- sb.append(">=");
- sb.append(lastBin);
- sb.append("ms=");
- sb.append(times.mTimes[NUM_LAUNCH_TIME_BINS-1]);
- }
- sb.append('\n');
- }
- final int NC = pus.componentResumeTimes.size();
- for (int c=0; c<NC; c++) {
- sb.append(" ");
- sb.append(pus.componentResumeTimes.keyAt(c));
- sb.append(" last resumed ");
- sb.append(DateFormat.format("yyyy-MM-dd-HH-mm-ss",
- pus.componentResumeTimes.valueAt(c)).toString());
- sb.append('\n');
- }
- }
-
- pw.write(sb.toString());
- }
- if (packages == null) {
- int NC = stats.mConfigurations.size();
- for (int c=0; c<NC; c++) {
- ConfigUsageStatsExtended cus
- = (ConfigUsageStatsExtended)stats.mConfigurations.valueAt(c);
- sb.setLength(0);
- if (isCompactOutput) {
- sb.append("C:"); sb.append(cus.getConfiguration().toString());
- sb.append(","); sb.append(cus.getUsageCount()); sb.append(",");
- sb.append(cus.getUsageTime(time));
- } else {
- sb.append(" ");
- sb.append(cus.getConfiguration().toString());
- sb.append(":\n");
- if (cus.getUsageCount() != 0 || cus.getUsageTime(time) != 0) {
- sb.append(" Used ");
- sb.append(cus.getUsageCount());
- sb.append(" times, ");
- TimeUtils.formatDuration(cus.getUsageTime(time), sb);
- sb.append("\n");
- }
- if (cus.getLastUsedTime() > 0) {
- sb.append(" Last used: ");
- sb.append(DateFormat.format("yyyy-MM-dd-HH-mm-ss",
- cus.getLastUsedTime()).toString());
- sb.append("\n");
- }
- }
- pw.write(sb.toString());
- }
- }
- }
-
- /**
- * Searches array of arguments for the specified string
- * @param args array of argument strings
- * @param value value to search for
- * @return true if the value is contained in the array
- */
- private static boolean scanArgs(String[] args, String value) {
- if (args != null) {
- for (String arg : args) {
- if (value.equals(arg)) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Searches array of arguments for the specified string's data
- * @param args array of argument strings
- * @param value value to search for
- * @return the string of data after the arg, or null if there is none
- */
- private static String scanArgsData(String[] args, String value) {
- if (args != null) {
- final int N = args.length;
- for (int i=0; i<N; i++) {
- if (value.equals(args[i])) {
- i++;
- return i < N ? args[i] : null;
- }
- }
- }
- return null;
- }
-
- /*
- * The data persisted to file is parsed and the stats are computed.
- */
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (mContext.checkCallingPermission(android.Manifest.permission.DUMP)
- != PackageManager.PERMISSION_GRANTED) {
- pw.println("Permission Denial: can't dump UsageStats from from pid="
- + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
- + " without permission " + android.Manifest.permission.DUMP);
- return;
- }
-
- final boolean isCheckinRequest = scanArgs(args, "--checkin");
- final boolean isCompactOutput = isCheckinRequest || scanArgs(args, "-c");
- final boolean deleteAfterPrint = isCheckinRequest || scanArgs(args, "-d");
- final String rawPackages = scanArgsData(args, "--packages");
-
- // Make sure the current stats are written to the file. This
- // doesn't need to be done if we are deleting files after printing,
- // since in that case we won't print the current stats.
- if (!deleteAfterPrint) {
- writeStatsToFile(true, false);
- }
-
- HashSet<String> packages = null;
- if (rawPackages != null) {
- if (!"*".equals(rawPackages)) {
- // A * is a wildcard to show all packages.
- String[] names = rawPackages.split(",");
- if (names.length != 0) {
- packages = new HashSet<String>();
- }
- for (String n : names) {
- packages.add(n);
- }
- }
- } else if (isCheckinRequest) {
- // If checkin doesn't specify any packages, then we simply won't
- // show anything.
- Slog.w(TAG, "Checkin without packages");
- return;
- }
-
- synchronized (mFileLock) {
- collectDumpInfoFLOCK(pw, isCompactOutput, deleteAfterPrint, packages);
- }
- }
-}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index de929cb..bf2edc8 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -20,6 +20,7 @@ import android.app.ActivityManagerNative;
import android.app.ActivityThread;
import android.app.IAlarmManager;
import android.app.INotificationManager;
+import android.app.usage.UsageStatsManagerInternal;
import android.bluetooth.BluetoothAdapter;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -91,6 +92,7 @@ import com.android.server.storage.DeviceStorageMonitorService;
import com.android.server.trust.TrustManagerService;
import com.android.server.tv.TvInputManagerService;
import com.android.server.twilight.TwilightService;
+import com.android.server.usage.UsageStatsService;
import com.android.server.usb.UsbService;
import com.android.server.wallpaper.WallpaperManagerService;
import com.android.server.webkit.WebViewUpdateService;
@@ -366,6 +368,11 @@ public final class SystemServer {
// Tracks the battery level. Requires LightService.
mSystemServiceManager.startService(BatteryService.class);
+
+ // Tracks application usage stats.
+ mSystemServiceManager.startService(UsageStatsService.class);
+ mActivityManagerService.setUsageStatsManager(
+ LocalServices.getService(UsageStatsManagerInternal.class));
}
/**
diff --git a/services/usage/Android.mk b/services/usage/Android.mk
new file mode 100644
index 0000000..d4b7fa8
--- /dev/null
+++ b/services/usage/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := services.usage
+
+LOCAL_SRC_FILES += \
+ $(call all-java-files-under,java)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
new file mode 100644
index 0000000..4e75f61
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -0,0 +1,199 @@
+/**
+ * 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.UsageStats;
+import android.app.usage.UsageStatsManager;
+import android.util.AtomicFile;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Calendar;
+
+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 TimeSparseArray<AtomicFile>[] mSortedStatFiles;
+ private final Calendar mCal;
+
+ public UsageStatsDatabase(File dir) {
+ mBucketDirs = new File[] {
+ new File(dir, "daily"),
+ new File(dir, "weekly"),
+ new File(dir, "monthly"),
+ new File(dir, "yearly"),
+ };
+ mSortedStatFiles = new TimeSparseArray[mBucketDirs.length];
+ mCal = Calendar.getInstance();
+ }
+
+ void init() {
+ synchronized (mLock) {
+ for (File f : mBucketDirs) {
+ f.mkdirs();
+ if (!f.exists()) {
+ throw new IllegalStateException("Failed to create directory "
+ + f.getAbsolutePath());
+ }
+ }
+
+ final FilenameFilter backupFileFilter = new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return !name.endsWith(".bak");
+ }
+ };
+
+ // 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);
+ if (files != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Found " + files.length + " stat files for bucket " + i);
+ }
+
+ for (File f : files) {
+ mSortedStatFiles[i].put(Long.parseLong(f.getName()), new AtomicFile(f));
+ }
+ }
+ }
+ }
+ }
+
+ public UsageStats getLatestUsageStats(int bucketType) {
+ synchronized (mLock) {
+ if (bucketType < 0 || bucketType >= mBucketDirs.length) {
+ throw new IllegalArgumentException("Bad bucket type " + bucketType);
+ }
+
+ final int fileCount = mSortedStatFiles[bucketType].size();
+ if (fileCount == 0) {
+ return null;
+ }
+
+ try {
+ final AtomicFile f = mSortedStatFiles[bucketType].valueAt(fileCount - 1);
+ UsageStats stats = UsageStatsXml.read(f);
+ stats.mLastTimeSaved = f.getLastModifiedTime();
+ return stats;
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to read usage stats file", e);
+ }
+ }
+ return null;
+ }
+
+ public UsageStats[] getUsageStats(int bucketType, long beginTime, int limit) {
+ synchronized (mLock) {
+ if (bucketType < 0 || bucketType >= mBucketDirs.length) {
+ throw new IllegalArgumentException("Bad bucket type " + bucketType);
+ }
+
+ if (limit <= 0) {
+ return UsageStats.EMPTY_STATS;
+ }
+
+ int startIndex = mSortedStatFiles[bucketType].closestIndexAfter(beginTime);
+ if (startIndex < 0) {
+ return UsageStats.EMPTY_STATS;
+ }
+
+ final int realLimit = Math.min(limit, mSortedStatFiles[bucketType].size() - startIndex);
+ try {
+ ArrayList<UsageStats> stats = new ArrayList<>(realLimit);
+ for (int i = 0; i < realLimit; i++) {
+ final AtomicFile f = mSortedStatFiles[bucketType].valueAt(startIndex + 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);
+ }
+ }
+ return stats.toArray(new UsageStats[stats.size()]);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to read usage stats file", e);
+ return UsageStats.EMPTY_STATS;
+ }
+ }
+ }
+
+ public void prune() {
+ synchronized (mLock) {
+ long timeNow = System.currentTimeMillis();
+
+ mCal.setTimeInMillis(timeNow);
+ mCal.add(Calendar.MONTH, -6);
+ pruneFilesOlderThan(mBucketDirs[UsageStatsManager.MONTHLY_BUCKET],
+ mCal.getTimeInMillis());
+
+ mCal.setTimeInMillis(timeNow);
+ mCal.add(Calendar.WEEK_OF_YEAR, -4);
+ pruneFilesOlderThan(mBucketDirs[UsageStatsManager.WEEKLY_BUCKET],
+ mCal.getTimeInMillis());
+
+ mCal.setTimeInMillis(timeNow);
+ mCal.add(Calendar.DAY_OF_YEAR, -7);
+ pruneFilesOlderThan(mBucketDirs[UsageStatsManager.DAILY_BUCKET],
+ mCal.getTimeInMillis());
+ }
+ }
+
+ private static void pruneFilesOlderThan(File dir, long expiryTime) {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File f : files) {
+ long beginTime = Long.parseLong(f.getName());
+ if (beginTime < expiryTime) {
+ new AtomicFile(f).delete();
+ }
+ }
+ }
+ }
+
+ public void putUsageStats(int bucketType, UsageStats stats)
+ throws IOException {
+ synchronized (mLock) {
+ if (bucketType < 0 || bucketType >= mBucketDirs.length) {
+ throw new IllegalArgumentException("Bad bucket type " + bucketType);
+ }
+
+ AtomicFile f = mSortedStatFiles[bucketType].get(stats.mBeginTimeStamp);
+ if (f == null) {
+ f = new AtomicFile(new File(mBucketDirs[bucketType],
+ Long.toString(stats.mBeginTimeStamp)));
+ mSortedStatFiles[bucketType].append(stats.mBeginTimeStamp, f);
+ }
+
+ UsageStatsXml.write(stats, f);
+ stats.mLastTimeSaved = f.getLastModifiedTime();
+ }
+ }
+
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
new file mode 100644
index 0000000..13cbf8a
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -0,0 +1,437 @@
+/**
+ * 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.AppOpsManager;
+import android.app.usage.IUsageStatsManager;
+import android.app.usage.PackageUsageStats;
+import android.app.usage.TimeSparseArray;
+import android.app.usage.UsageStats;
+import android.app.usage.UsageStatsManager;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.ArraySet;
+import android.util.Slog;
+import com.android.internal.os.BackgroundThread;
+import com.android.server.SystemService;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+
+public class UsageStatsService extends SystemService {
+ static final String TAG = "UsageStatsService";
+
+ 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 FLUSH_INTERVAL = DEBUG ? TEN_SECONDS : TWENTY_MINUTES;
+ private static final int USAGE_STAT_RESULT_LIMIT = 10;
+ private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+ // Handler message types.
+ static final int MSG_REPORT_EVENT = 0;
+ static final int MSG_FLUSH_TO_DISK = 1;
+
+ final Object mLock = new Object();
+ Handler mHandler;
+ AppOpsManager mAppOps;
+
+ private UsageStatsDatabase mDatabase;
+ private UsageStats[] mCurrentStats = new UsageStats[UsageStatsManager.BUCKET_COUNT];
+ private TimeSparseArray<UsageStats.Event> mCurrentEvents = new TimeSparseArray<>();
+ private boolean mStatsChanged = false;
+ private Calendar mDailyExpiryDate;
+
+ public UsageStatsService(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mDailyExpiryDate = Calendar.getInstance();
+ mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+ mHandler = new H(BackgroundThread.get().getLooper());
+
+ File systemDataDir = new File(Environment.getDataDirectory(), "system");
+ mDatabase = new UsageStatsDatabase(new File(systemDataDir, "usagestats"));
+ mDatabase.init();
+
+ synchronized (mLock) {
+ initLocked();
+ }
+
+ publishLocalService(UsageStatsManagerInternal.class, new LocalService());
+ publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService());
+ }
+
+ private void initLocked() {
+ int nullCount = 0;
+ for (int i = 0; i < mCurrentStats.length; i++) {
+ mCurrentStats[i] = mDatabase.getLatestUsageStats(i);
+ if (mCurrentStats[i] == null) {
+ nullCount++;
+ }
+ }
+
+ if (nullCount > 0) {
+ if (nullCount != mCurrentStats.length) {
+ // This is weird, but we shouldn't fail if something like this
+ // happens.
+ Slog.w(TAG, "Some stats have no latest available");
+ } else {
+ // This must be first boot.
+ }
+
+ // By calling loadActiveStatsLocked, we will
+ // generate new stats for each bucket.
+ loadActiveStatsLocked();
+ } else {
+ // Set up the expiry date to be one day from the latest daily stat.
+ // This may actually be today and we will rollover on the first event
+ // that is reported.
+ mDailyExpiryDate.setTimeInMillis(
+ mCurrentStats[UsageStatsManager.DAILY_BUCKET].mBeginTimeStamp);
+ mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1);
+ UsageStatsUtils.truncateDateTo(UsageStatsManager.DAILY_BUCKET, mDailyExpiryDate);
+ Slog.i(TAG, "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 (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) {
+ updateStatsLocked(stat, pkgStats.mPackageName, stat.mLastTimeSaved,
+ UsageStats.Event.END_OF_DAY);
+ notifyStatsChangedLocked();
+ }
+ }
+ }
+ }
+
+ private void rolloverStatsLocked() {
+ final long startTime = System.currentTimeMillis();
+ Slog.i(TAG, "Rolling over usage stats");
+
+ // 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 (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) {
+ continuePreviousDay.add(pkgStats.mPackageName);
+ updateStatsLocked(stat, pkgStats.mPackageName,
+ mDailyExpiryDate.getTimeInMillis() - 1, UsageStats.Event.END_OF_DAY);
+ mStatsChanged = true;
+ }
+ }
+ }
+
+ persistActiveStatsLocked();
+ mDatabase.prune();
+ loadActiveStatsLocked();
+
+ final int continueCount = continuePreviousDay.size();
+ for (int i = 0; i < continueCount; i++) {
+ String name = continuePreviousDay.valueAt(i);
+ for (UsageStats stat : mCurrentStats) {
+ updateStatsLocked(stat, name,
+ mCurrentStats[UsageStatsManager.DAILY_BUCKET].mBeginTimeStamp,
+ UsageStats.Event.CONTINUE_PREVIOUS_DAY);
+ mStatsChanged = true;
+ }
+ }
+ persistActiveStatsLocked();
+
+ final long totalTime = System.currentTimeMillis() - startTime;
+ Slog.i(TAG, "Rolling over usage stats complete. Took " + totalTime + " milliseconds");
+ }
+
+ private void notifyStatsChangedLocked() {
+ if (!mStatsChanged) {
+ mStatsChanged = true;
+ mHandler.sendEmptyMessageDelayed(MSG_FLUSH_TO_DISK, FLUSH_INTERVAL);
+ }
+ }
+
+ /**
+ * Called by the Bunder stub
+ */
+ void shutdown() {
+ synchronized (mLock) {
+ mHandler.removeMessages(MSG_REPORT_EVENT);
+ mHandler.removeMessages(MSG_FLUSH_TO_DISK);
+ persistActiveStatsLocked();
+ }
+ }
+
+ private static String eventToString(int eventType) {
+ switch (eventType) {
+ case UsageStats.Event.NONE:
+ return "NONE";
+ case UsageStats.Event.MOVE_TO_BACKGROUND:
+ return "MOVE_TO_BACKGROUND";
+ case UsageStats.Event.MOVE_TO_FOREGROUND:
+ return "MOVE_TO_FOREGROUND";
+ case UsageStats.Event.END_OF_DAY:
+ return "END_OF_DAY";
+ case UsageStats.Event.CONTINUE_PREVIOUS_DAY:
+ return "CONTINUE_PREVIOUS_DAY";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /**
+ * Called by the Binder stub.
+ */
+ void reportEvent(UsageStats.Event event) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "Got usage event for " + event.packageName
+ + "[" + event.timeStamp + "]: "
+ + eventToString(event.eventType));
+ }
+
+ if (event.timeStamp >= mDailyExpiryDate.getTimeInMillis()) {
+ // Need to rollover
+ rolloverStatsLocked();
+ }
+
+ mCurrentEvents.append(event.timeStamp, event);
+
+ for (UsageStats stats : mCurrentStats) {
+ updateStatsLocked(stats, event.packageName, event.timeStamp, event.eventType);
+ }
+ notifyStatsChangedLocked();
+ }
+ }
+
+ /**
+ * Called by the Binder stub.
+ */
+ UsageStats[] getUsageStats(int bucketType, long beginTime) {
+ if (bucketType < 0 || bucketType >= mCurrentStats.length) {
+ return UsageStats.EMPTY_STATS;
+ }
+
+ final long timeNow = System.currentTimeMillis();
+ if (beginTime > timeNow) {
+ return UsageStats.EMPTY_STATS;
+ }
+
+ synchronized (mLock) {
+ if (beginTime >= mCurrentStats[bucketType].mEndTimeStamp) {
+ if (DEBUG) {
+ Slog.d(TAG, "Requesting stats after " + beginTime + " but latest is "
+ + mCurrentStats[bucketType].mEndTimeStamp);
+ }
+ // Nothing newer available.
+ return UsageStats.EMPTY_STATS;
+ } else if (beginTime >= mCurrentStats[bucketType].mBeginTimeStamp) {
+ if (DEBUG) {
+ Slog.d(TAG, "Returning in-memory stats");
+ }
+ // 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.
+ persistActiveStatsLocked();
+ }
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "SELECT * FROM " + bucketType + " WHERE beginTime >= "
+ + beginTime + " LIMIT " + USAGE_STAT_RESULT_LIMIT);
+ }
+
+ UsageStats[] results = mDatabase.getUsageStats(bucketType, beginTime,
+ USAGE_STAT_RESULT_LIMIT);
+
+ if (DEBUG) {
+ Slog.d(TAG, "Results: " + results.length);
+ }
+ return results;
+ }
+
+ /**
+ * Called by the Binder stub.
+ */
+ UsageStats.Event[] getEvents(long time) {
+ return UsageStats.Event.EMPTY_EVENTS;
+ }
+
+ private void loadActiveStatsLocked() {
+ final long timeNow = System.currentTimeMillis();
+
+ Calendar tempCal = mDailyExpiryDate;
+ for (int i = 0; i < mCurrentStats.length; i++) {
+ tempCal.setTimeInMillis(timeNow);
+ UsageStatsUtils.truncateDateTo(i, tempCal);
+
+ if (mCurrentStats[i] != null &&
+ mCurrentStats[i].mBeginTimeStamp == 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];
+ } else {
+ mCurrentStats[i] = UsageStats.create(tempCal.getTimeInMillis(), timeNow);
+ }
+ }
+ mStatsChanged = false;
+ mDailyExpiryDate.setTimeInMillis(timeNow);
+ mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1);
+ UsageStatsUtils.truncateDateTo(UsageStatsManager.DAILY_BUCKET, mDailyExpiryDate);
+ Slog.i(TAG, "Rollover scheduled for " + sDateFormat.format(mDailyExpiryDate.getTime()));
+ }
+
+
+ private void persistActiveStatsLocked() {
+ if (mStatsChanged) {
+ Slog.i(TAG, "Flushing usage stats to disk");
+ try {
+ for (int i = 0; i < mCurrentStats.length; i++) {
+ mDatabase.putUsageStats(i, mCurrentStats[i]);
+ }
+ mStatsChanged = false;
+ mHandler.removeMessages(MSG_FLUSH_TO_DISK);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to persist active stats", e);
+ }
+ }
+ }
+
+ private void updateStatsLocked(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;
+ }
+
+ class H extends Handler {
+ public H(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_REPORT_EVENT:
+ reportEvent((UsageStats.Event) msg.obj);
+ break;
+
+ case MSG_FLUSH_TO_DISK:
+ synchronized (mLock) {
+ persistActiveStatsLocked();
+ }
+ break;
+
+ default:
+ super.handleMessage(msg);
+ break;
+ }
+ }
+ }
+
+ private class BinderService extends IUsageStatsManager.Stub {
+
+ @Override
+ public UsageStats[] getStatsSince(int bucketType, long time, String callingPackage) {
+ if (mAppOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS, Binder.getCallingUid(),
+ callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ return UsageStats.EMPTY_STATS;
+ }
+
+ long token = Binder.clearCallingIdentity();
+ try {
+ return getUsageStats(bucketType, time);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public UsageStats.Event[] getEventsSince(long time, String callingPackage) {
+ if (mAppOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS, Binder.getCallingUid(),
+ callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ return UsageStats.Event.EMPTY_EVENTS;
+ }
+
+ long token = Binder.clearCallingIdentity();
+ try {
+ return getEvents(time);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ /**
+ * This local service implementation is primarily used by ActivityManagerService.
+ * ActivityManagerService will call these methods holding the 'am' lock, which means we
+ * shouldn't be doing any IO work or other long running tasks in these methods.
+ */
+ private class LocalService extends UsageStatsManagerInternal {
+
+ @Override
+ public void reportEvent(ComponentName component, long timeStamp, int eventType) {
+ UsageStats.Event event = new UsageStats.Event(component.getPackageName(), timeStamp,
+ eventType);
+ mHandler.obtainMessage(MSG_REPORT_EVENT, event).sendToTarget();
+ }
+
+ @Override
+ public void prepareShutdown() {
+ // This method *WILL* do IO work, but we must block until it is finished or else
+ // we might not shutdown cleanly. This is ok to do with the 'am' lock held, because
+ // we are shutting down.
+ shutdown();
+ }
+ }
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsUtils.java b/services/usage/java/com/android/server/usage/UsageStatsUtils.java
new file mode 100644
index 0000000..887e016
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/UsageStatsUtils.java
@@ -0,0 +1,63 @@
+/**
+ * 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.UsageStatsManager;
+
+import java.util.Calendar;
+
+/**
+ * A collection of utility methods used by the UsageStatsService and accompanying classes.
+ */
+final class UsageStatsUtils {
+ private UsageStatsUtils() {}
+
+ /**
+ * 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,
+ * with the time set to 00:00:00.
+ *
+ * @param bucket The UsageStats bucket to truncate to.
+ * @param cal The date to truncate.
+ */
+ public static void truncateDateTo(int bucket, Calendar cal) {
+ cal.set(Calendar.HOUR_OF_DAY, 0);
+ cal.set(Calendar.MINUTE, 0);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+
+ switch (bucket) {
+ case UsageStatsManager.YEARLY_BUCKET:
+ cal.set(Calendar.DAY_OF_YEAR, 0);
+ break;
+
+ case UsageStatsManager.MONTHLY_BUCKET:
+ cal.set(Calendar.DAY_OF_MONTH, 0);
+ break;
+
+ case UsageStatsManager.WEEKLY_BUCKET:
+ cal.set(Calendar.DAY_OF_WEEK, 0);
+ break;
+
+ case UsageStatsManager.DAILY_BUCKET:
+ break;
+
+ default:
+ throw new UnsupportedOperationException("Can't truncate date to bucket " + bucket);
+ }
+ }
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXml.java b/services/usage/java/com/android/server/usage/UsageStatsXml.java
new file mode 100644
index 0000000..78f89d0
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/UsageStatsXml.java
@@ -0,0 +1,161 @@
+/**
+ * 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.UsageStats;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+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;
+
+ public static UsageStats read(AtomicFile file) throws IOException {
+ try {
+ FileInputStream in = file.openRead();
+ try {
+ return read(in);
+ } finally {
+ try {
+ in.close();
+ } catch (IOException e) {
+ // Empty
+ }
+ }
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "UsageStats Xml", e);
+ throw e;
+ }
+ }
+
+ 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 UsageStats read(InputStream in) throws IOException {
+ XmlPullParser parser = Xml.newPullParser();
+ try {
+ parser.setInput(in, "utf-8");
+ XmlUtils.beginDocument(parser, USAGESTATS_TAG);
+ String versionStr = parser.getAttributeValue(null, VERSION_ATTR);
+ try {
+ switch (Integer.parseInt(versionStr)) {
+ case 1:
+ return loadVersion1(parser);
+ default:
+ Slog.e(TAG, "Unrecognized version " + versionStr);
+ throw new IOException("Unrecognized version " + versionStr);
+ }
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Bad version");
+ throw new IOException(e);
+ }
+ } catch (XmlPullParserException e) {
+ Slog.e(TAG, "Failed to parse Xml", e);
+ throw new IOException(e);
+ }
+ }
+
+ 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 {
+ 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.
+
+ xml.endTag(null, USAGESTATS_TAG);
+ xml.endDocument();
+ }
+}