Loading core/java/android/app/usage/UsageStatsManager.java +0 −1 Original line number Diff line number Diff line Loading @@ -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) { Loading core/proto/android/server/usagestatsservice_v2.proto +9 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; } /** Loading services/usage/java/com/android/server/usage/UsageStatsProtoV2.java +72 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; /** Loading Loading @@ -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); } } } services/usage/java/com/android/server/usage/UsageStatsService.java +68 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -716,6 +724,7 @@ public class UsageStatsService extends SystemService implements // orderly shutdown, the last event is DEVICE_SHUTDOWN. reportEventToAllUserId(event); flushToDiskLocked(); persistGlobalComponentUsageLocked(); } mAppStandby.flushToDisk(); Loading Loading @@ -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(); Loading Loading @@ -1483,7 +1546,11 @@ public class UsageStatsService extends SystemService implements } break; } case MSG_ON_START: synchronized (mLock) { loadGlobalComponentUsageLocked(); } break; default: super.handleMessage(msg); break; Loading Loading
core/java/android/app/usage/UsageStatsManager.java +0 −1 Original line number Diff line number Diff line Loading @@ -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) { Loading
core/proto/android/server/usagestatsservice_v2.proto +9 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; } /** Loading
services/usage/java/com/android/server/usage/UsageStatsProtoV2.java +72 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; /** Loading Loading @@ -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); } } }
services/usage/java/com/android/server/usage/UsageStatsService.java +68 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -716,6 +724,7 @@ public class UsageStatsService extends SystemService implements // orderly shutdown, the last event is DEVICE_SHUTDOWN. reportEventToAllUserId(event); flushToDiskLocked(); persistGlobalComponentUsageLocked(); } mAppStandby.flushToDisk(); Loading Loading @@ -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(); Loading Loading @@ -1483,7 +1546,11 @@ public class UsageStatsService extends SystemService implements } break; } case MSG_ON_START: synchronized (mLock) { loadGlobalComponentUsageLocked(); } break; default: super.handleMessage(msg); break; Loading