summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZhen Zhang <zzhen@google.com>2021-03-23 14:50:21 -0700
committerZhen Zhang <zzhen@google.com>2021-03-31 11:27:26 -0700
commit131f53fdf4cf6ee29cc8f5a04946da8e5f926687 (patch)
tree321e94f5c7f9bbe221af5324683655e02ee78093
parenteed249828f54233fb382b06a84a245e9ead19447 (diff)
Persist user-agnostic lastTimeAnyComponentUsed in disk
Persist the user-agnostic lastTimeAnyComponentUsed usage stats in disk. Bug: 183462940 Test: atest UsageStatsServiceTest Test: atest UserStatsPersistenceTest Test: atest CtsUsageStatsTestCases:UsageStatsTest Change-Id: Ibe946f8e9fd9fa7d9ba546da67a6040c00e0f9ea
-rw-r--r--core/java/android/app/usage/UsageStatsManager.java1
-rw-r--r--core/proto/android/server/usagestatsservice_v2.proto9
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsProtoV2.java72
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java69
4 files changed, 149 insertions, 2 deletions
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 067c212532d3..542473a3d2a9 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -1272,7 +1272,6 @@ public final class UsageStatsManager {
android.Manifest.permission.INTERACT_ACROSS_USERS,
android.Manifest.permission.PACKAGE_USAGE_STATS})
public long getLastTimeAnyComponentUsed(@NonNull String packageName) {
- // TODO(b/183462940): This usage data is not persisted to disk yet.
try {
return mService.getLastTimeAnyComponentUsed(packageName);
} catch (RemoteException re) {
diff --git a/core/proto/android/server/usagestatsservice_v2.proto b/core/proto/android/server/usagestatsservice_v2.proto
index 3e5cd92c2533..d5cf60d94e3a 100644
--- a/core/proto/android/server/usagestatsservice_v2.proto
+++ b/core/proto/android/server/usagestatsservice_v2.proto
@@ -40,6 +40,13 @@ message IntervalStatsObfuscatedProto {
optional bool active = 5;
}
+
+ // Stores the information of last time a package is used by any users
+ message PackageUsage {
+ optional string package_name = 1;
+ optional int64 time_ms = 2;
+ }
+
// The following fields contain supplemental data used to build IntervalStats.
optional int64 end_time_ms = 1;
optional int32 major_version = 2;
@@ -57,6 +64,8 @@ message IntervalStatsObfuscatedProto {
repeated EventObfuscatedProto event_log = 22;
// The following field is only used to persist the reported events before a user unlock
repeated PendingEventProto pending_events = 23;
+ // The following field is only used to persist the user-agnostic package usage before shut down
+ repeated PackageUsage package_usage = 24;
}
/**
diff --git a/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java b/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java
index 5c5667cf0389..076eaf84de6a 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java
@@ -19,6 +19,7 @@ import android.app.usage.ConfigurationStats;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStats;
import android.content.res.Configuration;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -30,6 +31,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedList;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
@@ -812,4 +814,74 @@ final class UsageStatsProtoV2 {
}
proto.flush();
}
+
+ private static Pair<String, Long> parseGlobalComponentUsage(ProtoInputStream proto)
+ throws IOException {
+ String packageName = "";
+ long time = 0;
+ while (true) {
+ switch (proto.nextField()) {
+ case (int) IntervalStatsObfuscatedProto.PackageUsage.PACKAGE_NAME:
+ packageName = proto.readString(
+ IntervalStatsObfuscatedProto.PackageUsage.PACKAGE_NAME);
+ break;
+ case (int) IntervalStatsObfuscatedProto.PackageUsage.TIME_MS:
+ time = proto.readLong(IntervalStatsObfuscatedProto.PackageUsage.TIME_MS);
+ break;
+ case ProtoInputStream.NO_MORE_FIELDS:
+ return new Pair<>(packageName, time);
+ }
+ }
+ }
+
+ /**
+ * Populates the map of latest package usage from the input stream given.
+ *
+ * @param in the input stream from which to read the package usage.
+ * @param lastTimeComponentUsedGlobal the map of package's global component usage to populate.
+ */
+ static void readGlobalComponentUsage(InputStream in,
+ Map<String, Long> lastTimeComponentUsedGlobal) throws IOException {
+ final ProtoInputStream proto = new ProtoInputStream(in);
+ while (true) {
+ switch (proto.nextField()) {
+ case (int) IntervalStatsObfuscatedProto.PACKAGE_USAGE:
+ try {
+ final long token = proto.start(IntervalStatsObfuscatedProto.PACKAGE_USAGE);
+ final Pair<String, Long> usage = parseGlobalComponentUsage(proto);
+ proto.end(token);
+ if (!usage.first.isEmpty() && usage.second > 0) {
+ lastTimeComponentUsedGlobal.put(usage.first, usage.second);
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to parse some package usage from proto.", e);
+ }
+ break;
+ case ProtoInputStream.NO_MORE_FIELDS:
+ return;
+ }
+ }
+ }
+
+ /**
+ * Writes the user-agnostic last time package usage to a ProtoBuf file.
+ *
+ * @param out the output stream to which to write the package usage
+ * @param lastTimeComponentUsedGlobal the map storing the global component usage of packages
+ */
+ static void writeGlobalComponentUsage(OutputStream out,
+ Map<String, Long> lastTimeComponentUsedGlobal) {
+ final ProtoOutputStream proto = new ProtoOutputStream(out);
+ final Map.Entry<String, Long>[] entries =
+ (Map.Entry<String, Long>[]) lastTimeComponentUsedGlobal.entrySet().toArray();
+ final int size = entries.length;
+ for (int i = 0; i < size; ++i) {
+ if (entries[i].getValue() <= 0) continue;
+ final long token = proto.start(IntervalStatsObfuscatedProto.PACKAGE_USAGE);
+ proto.write(IntervalStatsObfuscatedProto.PackageUsage.PACKAGE_NAME,
+ entries[i].getKey());
+ proto.write(IntervalStatsObfuscatedProto.PackageUsage.TIME_MS, entries[i].getValue());
+ proto.end(token);
+ }
+ }
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 52f9fe52a5fa..a6b68e1e2ab2 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -75,6 +75,7 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -142,6 +143,10 @@ public class UsageStatsService extends SystemService implements
// For migration purposes, indicates whether to keep the legacy usage stats directory or not
private static final boolean KEEP_LEGACY_DIR = false;
+ private static final File COMMON_USAGE_STATS_DE_DIR =
+ new File(Environment.getDataSystemDeDirectory(), "usagestats");
+ private static final String GLOBAL_COMPONENT_USAGE_FILE_NAME = "globalcomponentusage";
+
private static final char TOKEN_DELIMITER = '/';
// Handler message types.
@@ -152,6 +157,7 @@ public class UsageStatsService extends SystemService implements
static final int MSG_REPORT_EVENT_TO_ALL_USERID = 4;
static final int MSG_UNLOCKED_USER = 5;
static final int MSG_PACKAGE_REMOVED = 6;
+ static final int MSG_ON_START = 7;
private final Object mLock = new Object();
Handler mHandler;
@@ -293,6 +299,8 @@ public class UsageStatsService extends SystemService implements
publishLocalService(UsageStatsManagerInternal.class, new LocalService());
publishLocalService(AppStandbyInternal.class, mAppStandby);
publishBinderServices();
+
+ mHandler.obtainMessage(MSG_ON_START).sendToTarget();
}
@VisibleForTesting
@@ -716,6 +724,7 @@ public class UsageStatsService extends SystemService implements
// orderly shutdown, the last event is DEVICE_SHUTDOWN.
reportEventToAllUserId(event);
flushToDiskLocked();
+ persistGlobalComponentUsageLocked();
}
mAppStandby.flushToDisk();
@@ -794,6 +803,60 @@ public class UsageStatsService extends SystemService implements
}
}
+ private void loadGlobalComponentUsageLocked() {
+ final File[] packageUsageFile = COMMON_USAGE_STATS_DE_DIR.listFiles(
+ (dir, name) -> TextUtils.equals(name, GLOBAL_COMPONENT_USAGE_FILE_NAME));
+ if (packageUsageFile == null || packageUsageFile.length == 0) {
+ return;
+ }
+
+ final AtomicFile af = new AtomicFile(packageUsageFile[0]);
+ final Map<String, Long> tmpUsage = new ArrayMap<>();
+ try {
+ try (FileInputStream in = af.openRead()) {
+ UsageStatsProtoV2.readGlobalComponentUsage(in, tmpUsage);
+ }
+ // only add to in memory map if the read was successful
+ final Map.Entry<String, Long>[] entries =
+ (Map.Entry<String, Long>[]) tmpUsage.entrySet().toArray();
+ final int size = entries.length;
+ for (int i = 0; i < size; ++i) {
+ // In memory data is usually the most up-to-date, so skip the packages which already
+ // have usage data.
+ mLastTimeComponentUsedGlobal.putIfAbsent(
+ entries[i].getKey(), entries[i].getValue());
+ }
+ } catch (Exception e) {
+ // Most likely trying to read a corrupted file - log the failure
+ Slog.e(TAG, "Could not read " + packageUsageFile[0]);
+ }
+ }
+
+ private void persistGlobalComponentUsageLocked() {
+ if (mLastTimeComponentUsedGlobal.isEmpty()) {
+ return;
+ }
+
+ if (!COMMON_USAGE_STATS_DE_DIR.mkdirs() && !COMMON_USAGE_STATS_DE_DIR.exists()) {
+ throw new IllegalStateException("Common usage stats DE directory does not exist: "
+ + COMMON_USAGE_STATS_DE_DIR.getAbsolutePath());
+ }
+ final File lastTimePackageFile = new File(COMMON_USAGE_STATS_DE_DIR,
+ GLOBAL_COMPONENT_USAGE_FILE_NAME);
+ final AtomicFile af = new AtomicFile(lastTimePackageFile);
+ FileOutputStream fos = null;
+ try {
+ fos = af.startWrite();
+ UsageStatsProtoV2.writeGlobalComponentUsage(fos, mLastTimeComponentUsedGlobal);
+ af.finishWrite(fos);
+ fos = null;
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to write " + lastTimePackageFile.getAbsolutePath());
+ } finally {
+ af.failWrite(fos); // when fos is null (successful write), this will no-op
+ }
+ }
+
private void reportEventOrAddToQueue(int userId, Event event) {
if (mUserUnlockedStates.contains(userId)) {
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
@@ -1483,7 +1546,11 @@ public class UsageStatsService extends SystemService implements
}
break;
}
-
+ case MSG_ON_START:
+ synchronized (mLock) {
+ loadGlobalComponentUsageLocked();
+ }
+ break;
default:
super.handleMessage(msg);
break;