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
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user