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:
Zhen Zhang
2021-03-23 14:50:21 -07:00
parent eed249828f
commit 131f53fdf4
4 changed files with 149 additions and 2 deletions

View File

@@ -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) {

View File

@@ -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;
}
/**

View File

@@ -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);
}
}
}

View File

@@ -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;