diff options
author | Zhen Zhang <zzhen@google.com> | 2021-03-23 14:50:21 -0700 |
---|---|---|
committer | Zhen Zhang <zzhen@google.com> | 2021-03-31 11:27:26 -0700 |
commit | 131f53fdf4cf6ee29cc8f5a04946da8e5f926687 (patch) | |
tree | 321e94f5c7f9bbe221af5324683655e02ee78093 | |
parent | eed249828f54233fb382b06a84a245e9ead19447 (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
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; |