Obfuscate usage stats data stored on disk.

All of the usage stats data stored on disk will now be obfuscated. There
will be a package to tokens mappings file stored on disk which has a
hierarchy of mappings for each string in each package's usage stats
data. A UsageStatsProtoV2 was added to keep the logic clean and separate
from the original usage stats proto parser.

Initial observations show a memory gain of over 60% w.r.t. the usage
stats data size on disk. There is also no performance hit because of this
change - in fact, even with the obfuscation overhead, reads are now over
65% faster and writes are up to 50% faster.

Bug: 135484470
Test: atest UsageStatsTest
Test: atest UsageStatsDatabaseTest
Test: atest UsageStatsDatabasePerfTest
Change-Id: I55ce729033d8b6e4051271802d57c72684053c32
This commit is contained in:
Varun Shah
2019-08-27 17:11:25 -07:00
parent 76e56e4039
commit e1ba9cde53
13 changed files with 1534 additions and 95 deletions

View File

@@ -311,18 +311,33 @@ public final class UsageEvents implements Parcelable {
*/
public static final int VALID_FLAG_BITS = FLAG_IS_PACKAGE_INSTANT_APP;
/**
* @hide
*/
private static final int UNASSIGNED_TOKEN = -1;
/**
* {@hide}
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public String mPackage;
/**
* {@hide}
*/
public int mPackageToken = UNASSIGNED_TOKEN;
/**
* {@hide}
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public String mClass;
/**
* {@hide}
*/
public int mClassToken = UNASSIGNED_TOKEN;
/**
* {@hide}
*/
@@ -333,11 +348,21 @@ public final class UsageEvents implements Parcelable {
*/
public String mTaskRootPackage;
/**
* {@hide}
*/
public int mTaskRootPackageToken = UNASSIGNED_TOKEN;
/**
* {@hide}
*/
public String mTaskRootClass;
/**
* {@hide}
*/
public int mTaskRootClassToken = UNASSIGNED_TOKEN;
/**
* {@hide}
*/
@@ -364,6 +389,11 @@ public final class UsageEvents implements Parcelable {
*/
public String mShortcutId;
/**
* {@hide}
*/
public int mShortcutIdToken = UNASSIGNED_TOKEN;
/**
* Action type passed to ChooserActivity
* Only present for {@link #CHOOSER_ACTION} event types.
@@ -401,6 +431,11 @@ public final class UsageEvents implements Parcelable {
*/
public String mNotificationChannelId;
/**
* {@hide}
*/
public int mNotificationChannelIdToken = UNASSIGNED_TOKEN;
/** @hide */
@EventFlags
public int mFlags;

View File

@@ -35,6 +35,7 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
import android.util.SparseArray;
import android.util.SparseIntArray;
/**
@@ -49,6 +50,11 @@ public final class UsageStats implements Parcelable {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public String mPackageName;
/**
* {@hide}
*/
public int mPackageToken = -1;
/**
* {@hide}
*/
@@ -140,6 +146,11 @@ public final class UsageStats implements Parcelable {
*/
public ArrayMap<String, ArrayMap<String, Integer>> mChooserCounts = new ArrayMap<>();
/**
* {@hide}
*/
public SparseArray<SparseIntArray> mChooserCountsObfuscated = new SparseArray<>();
/**
* {@hide}
*/

View File

@@ -114,6 +114,4 @@ message IntervalStatsProto {
repeated UsageStats packages = 20;
repeated Configuration configurations = 21;
repeated Event event_log = 22;
repeated Event pending_events = 23; // TODO: move to usagestatsservice_v2.proto
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
syntax = "proto2";
package com.android.server.usage;
import "frameworks/base/core/proto/android/content/configuration.proto";
import "frameworks/base/core/proto/android/privacy.proto";
option java_multiple_files = true;
/**
* Obfuscated version of android.service.IntervalStatsProto (usagestatsservice.proto).
*/
message IntervalStatsObfuscatedProto {
message CountAndTime {
optional int32 count = 1;
optional int64 time_ms = 2;
}
// Stores the relevant information an IntervalStats will have about a Configuration
message Configuration {
optional .android.content.ConfigurationProto config = 1;
optional int64 last_time_active_ms = 2;
optional int64 total_time_active_ms = 3;
optional int32 count = 4;
optional bool active = 5;
}
// The following fields contain supplemental data used to build IntervalStats.
optional int64 end_time_ms = 1;
optional int32 major_version = 2;
optional int32 minor_version = 3;
// The following fields contain aggregated usage stats data
optional CountAndTime interactive = 10;
optional CountAndTime non_interactive = 11;
optional CountAndTime keyguard_shown = 12;
optional CountAndTime keyguard_hidden = 13;
// The following fields contain listed usage stats data
repeated UsageStatsObfuscatedProto packages = 20;
repeated Configuration configurations = 21;
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;
}
/**
* Stores the relevant information from an obfuscated UsageStats.
*/
message UsageStatsObfuscatedProto {
message ChooserAction {
message CategoryCount {
optional int32 category_token = 1;
optional int32 count = 2;
}
optional int32 action_token = 1;
repeated CategoryCount counts = 2;
}
optional int32 package_token = 1;
optional int64 last_time_active_ms = 3;
optional int64 total_time_active_ms = 4;
optional int32 last_event = 5;
optional int32 app_launch_count = 6;
repeated ChooserAction chooser_actions = 7;
optional int64 last_time_service_used_ms = 8;
optional int64 total_time_service_used_ms = 9;
optional int64 last_time_visible_ms = 10;
optional int64 total_time_visible_ms = 11;
}
/**
* Stores the relevant information from an obfuscated Event.
*/
message EventObfuscatedProto {
optional int32 package_token = 1;
optional int32 class_token = 2;
optional int64 time_ms = 3;
optional int32 flags = 4;
optional int32 type = 5;
optional .android.content.ConfigurationProto config = 6;
optional int32 shortcut_id_token = 7;
optional int32 standby_bucket = 8;
optional int32 notification_channel_id_token = 9;
optional int32 instance_id = 10;
optional int32 task_root_package_token = 11;
optional int32 task_root_class_token = 12;
}
/**
* This message stores all of the fields in an Event object as strings instead of tokens.
*/
message PendingEventProto {
optional string package_name = 1;
optional string class_name = 2;
optional int64 time_ms = 3;
optional int32 flags = 4;
optional int32 type = 5;
optional .android.content.ConfigurationProto config = 6;
optional string shortcut_id = 7;
optional int32 standby_bucket = 8;
optional string notification_channel_id = 9;
optional int32 instance_id = 10;
optional string task_root_package = 11;
optional string task_root_class = 12;
}
/**
* A proto message representing the obfuscated tokens mappings for Usage Stats.
*/
message ObfuscatedPackagesProto {
message PackagesMap {
optional int32 package_token = 1;
// The list of strings for each package where their indices are the token
repeated string strings = 2;
}
optional int32 counter = 1;
// Stores the mappings for every package
repeated PackagesMap packages_map = 2;
}

View File

@@ -48,7 +48,7 @@ import java.util.Locale;
@SmallTest
public class UsageStatsDatabaseTest {
private static final int MAX_TESTED_VERSION = 4;
private static final int MAX_TESTED_VERSION = 5;
protected Context mContext;
private UsageStatsDatabase mUsageStatsDatabase;
private File mTestDir;
@@ -259,6 +259,24 @@ public class UsageStatsDatabaseTest {
void compareUsageEvent(Event e1, Event e2, int debugId, int minVersion) {
switch (minVersion) {
case 5: // test fields added in version 5
assertEquals(e1.mPackageToken, e2.mPackageToken, "Usage event " + debugId);
assertEquals(e1.mClassToken, e2.mClassToken, "Usage event " + debugId);
assertEquals(e1.mTaskRootPackageToken, e2.mTaskRootPackageToken,
"Usage event " + debugId);
assertEquals(e1.mTaskRootClassToken, e2.mTaskRootClassToken,
"Usage event " + debugId);
switch (e1.mEventType) {
case Event.SHORTCUT_INVOCATION:
assertEquals(e1.mShortcutIdToken, e2.mShortcutIdToken,
"Usage event " + debugId);
break;
case Event.NOTIFICATION_INTERRUPTION:
assertEquals(e1.mNotificationChannelIdToken, e2.mNotificationChannelIdToken,
"Usage event " + debugId);
break;
}
// fallthrough
case 4: // test fields added in version 4
assertEquals(e1.mInstanceId, e2.mInstanceId, "Usage event " + debugId);
assertEquals(e1.mTaskRootPackage, e2.mTaskRootPackage, "Usage event " + debugId);
@@ -372,6 +390,9 @@ public class UsageStatsDatabaseTest {
UsageStatsDatabase prevDB = new UsageStatsDatabase(mTestDir, oldVersion);
prevDB.init(1);
prevDB.putUsageStats(interval, mIntervalStats);
if (oldVersion >= 5) {
prevDB.writeMappingsLocked();
}
// Simulate an upgrade to a new version and read from the disk
UsageStatsDatabase newDB = new UsageStatsDatabase(mTestDir, newVersion);
@@ -438,6 +459,28 @@ public class UsageStatsDatabaseTest {
runVersionChangeTest(3, 4, UsageStatsManager.INTERVAL_YEARLY);
}
/**
* Test the version upgrade from 4 to 5
*/
@Test
public void testVersionUpgradeFrom4to5() throws IOException {
runVersionChangeTest(4, 5, UsageStatsManager.INTERVAL_DAILY);
runVersionChangeTest(4, 5, UsageStatsManager.INTERVAL_WEEKLY);
runVersionChangeTest(4, 5, UsageStatsManager.INTERVAL_MONTHLY);
runVersionChangeTest(4, 5, UsageStatsManager.INTERVAL_YEARLY);
}
/**
* Test the version upgrade from 3 to 5
*/
@Test
public void testVersionUpgradeFrom3to5() throws IOException {
runVersionChangeTest(3, 5, UsageStatsManager.INTERVAL_DAILY);
runVersionChangeTest(3, 5, UsageStatsManager.INTERVAL_WEEKLY);
runVersionChangeTest(3, 5, UsageStatsManager.INTERVAL_MONTHLY);
runVersionChangeTest(3, 5, UsageStatsManager.INTERVAL_YEARLY);
}
/**
* Test the version upgrade from 3 to 4
@@ -492,4 +535,30 @@ public class UsageStatsDatabaseTest {
assertEquals(extra, files.keyAt(0));
}
}
private void compareObfuscatedData(int interval) throws IOException {
// Write IntervalStats to disk
UsageStatsDatabase prevDB = new UsageStatsDatabase(mTestDir, 5);
prevDB.init(1);
prevDB.putUsageStats(interval, mIntervalStats);
prevDB.writeMappingsLocked();
// Read IntervalStats from disk into a new db
UsageStatsDatabase newDB = new UsageStatsDatabase(mTestDir, 5);
newDB.init(mEndTime);
List<IntervalStats> stats = newDB.queryUsageStats(interval, 0, mEndTime,
mIntervalStatsVerifier);
assertEquals(1, stats.size());
// The written and read IntervalStats should match
compareIntervalStats(mIntervalStats, stats.get(0), 5);
}
@Test
public void testObfuscation() throws IOException {
compareObfuscatedData(UsageStatsManager.INTERVAL_DAILY);
compareObfuscatedData(UsageStatsManager.INTERVAL_WEEKLY);
compareObfuscatedData(UsageStatsManager.INTERVAL_MONTHLY);
compareObfuscatedData(UsageStatsManager.INTERVAL_YEARLY);
}
}

View File

@@ -42,8 +42,11 @@ import android.app.usage.EventStats;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStats;
import android.content.res.Configuration;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoInputStream;
import com.android.internal.annotations.VisibleForTesting;
@@ -64,6 +67,8 @@ public class IntervalStats {
public final EventTracker keyguardShownTracker = new EventTracker();
public final EventTracker keyguardHiddenTracker = new EventTracker();
public final ArrayMap<String, UsageStats> packageStats = new ArrayMap<>();
/** @hide */
public final SparseArray<UsageStats> packageStatsObfuscated = new SparseArray<>();
public final ArrayMap<Configuration, ConfigurationStats> configurations = new ArrayMap<>();
public Configuration activeConfiguration;
public final EventList events = new EventList();
@@ -436,4 +441,182 @@ public class IntervalStats {
*/
majorVersion = CURRENT_MAJOR_VERSION;
}
/**
* Parses all of the tokens to strings in the obfuscated usage stats data. This includes
* deobfuscating each of the package tokens and chooser actions and categories.
*/
private void deobfuscateUsageStats(PackagesTokenData packagesTokenData) {
final int usageStatsSize = packageStatsObfuscated.size();
for (int statsIndex = 0; statsIndex < usageStatsSize; statsIndex++) {
final int packageToken = packageStatsObfuscated.keyAt(statsIndex);
final UsageStats usageStats = packageStatsObfuscated.valueAt(statsIndex);
usageStats.mPackageName = packagesTokenData.getString(packageToken,
PackagesTokenData.PACKAGE_NAME_INDEX);
// Update chooser counts
final int chooserActionsSize = usageStats.mChooserCountsObfuscated.size();
for (int actionIndex = 0; actionIndex < chooserActionsSize; actionIndex++) {
final ArrayMap<String, Integer> categoryCountsMap = new ArrayMap<>();
final int actionToken = usageStats.mChooserCountsObfuscated.keyAt(actionIndex);
final String action = packagesTokenData.getString(packageToken, actionToken);
final SparseIntArray categoryCounts =
usageStats.mChooserCountsObfuscated.valueAt(actionIndex);
final int categoriesSize = categoryCounts.size();
for (int categoryIndex = 0; categoryIndex < categoriesSize; categoryIndex++) {
final int categoryToken = categoryCounts.keyAt(categoryIndex);
final String category = packagesTokenData.getString(packageToken,
categoryToken);
categoryCountsMap.put(category, categoryCounts.valueAt(categoryIndex));
}
usageStats.mChooserCounts.put(action, categoryCountsMap);
}
packageStats.put(usageStats.mPackageName, usageStats);
}
}
/**
* Parses all of the tokens to strings in the obfuscated events data. This includes
* deobfuscating the package token, along with any class, task root package/class tokens, and
* shortcut or notification channel tokens.
*/
private void deobfuscateEvents(PackagesTokenData packagesTokenData) {
final int eventsSize = this.events.size();
for (int i = 0; i < eventsSize; i++) {
final Event event = this.events.get(i);
final int packageToken = event.mPackageToken;
event.mPackage = packagesTokenData.getString(packageToken,
PackagesTokenData.PACKAGE_NAME_INDEX);
if (event.mClassToken != PackagesTokenData.UNASSIGNED_TOKEN) {
event.mClass = packagesTokenData.getString(packageToken, event.mClassToken);
}
if (event.mTaskRootPackageToken != PackagesTokenData.UNASSIGNED_TOKEN) {
event.mTaskRootPackage = packagesTokenData.getString(packageToken,
event.mTaskRootPackageToken);
}
if (event.mTaskRootClassToken != PackagesTokenData.UNASSIGNED_TOKEN) {
event.mTaskRootClass = packagesTokenData.getString(packageToken,
event.mTaskRootClassToken);
}
switch (event.mEventType) {
case CONFIGURATION_CHANGE:
if (event.mConfiguration == null) {
event.mConfiguration = new Configuration();
}
break;
case SHORTCUT_INVOCATION:
event.mShortcutId = packagesTokenData.getString(packageToken,
event.mShortcutIdToken);
break;
case NOTIFICATION_INTERRUPTION:
event.mNotificationChannelId = packagesTokenData.getString(packageToken,
event.mNotificationChannelIdToken);
break;
}
}
}
/**
* Parses the obfuscated tokenized data held in this interval stats object.
*
* @hide
*/
public void deobfuscateData(PackagesTokenData packagesTokenData) {
deobfuscateUsageStats(packagesTokenData);
deobfuscateEvents(packagesTokenData);
}
/**
* Obfuscates certain strings within each package stats such as the package name, and the
* chooser actions and categories.
*/
private void obfuscateUsageStatsData(PackagesTokenData packagesTokenData) {
final int usageStatsSize = packageStats.size();
for (int statsIndex = 0; statsIndex < usageStatsSize; statsIndex++) {
final String packageName = packageStats.keyAt(statsIndex);
final UsageStats usageStats = packageStats.valueAt(statsIndex);
if (usageStats == null) {
continue;
}
final int packageToken = packagesTokenData.getPackageTokenOrAdd(packageName);
usageStats.mPackageToken = packageToken;
// Update chooser counts.
final int chooserActionsSize = usageStats.mChooserCounts.size();
for (int actionIndex = 0; actionIndex < chooserActionsSize; actionIndex++) {
final String action = usageStats.mChooserCounts.keyAt(actionIndex);
final ArrayMap<String, Integer> categoriesMap =
usageStats.mChooserCounts.valueAt(actionIndex);
if (categoriesMap == null) {
continue;
}
final SparseIntArray categoryCounts = new SparseIntArray();
final int categoriesSize = categoriesMap.size();
for (int categoryIndex = 0; categoryIndex < categoriesSize; categoryIndex++) {
String category = categoriesMap.keyAt(categoryIndex);
int categoryToken = packagesTokenData.getTokenOrAdd(packageToken, packageName,
category);
categoryCounts.put(categoryToken, categoriesMap.valueAt(categoryIndex));
}
int actionToken = packagesTokenData.getTokenOrAdd(packageToken, packageName,
action);
usageStats.mChooserCountsObfuscated.put(actionToken, categoryCounts);
}
packageStatsObfuscated.put(packageToken, usageStats);
}
}
/**
* Obfuscates certain strings within an event such as the package name, the class name,
* task root package and class names, and shortcut and notification channel ids.
*/
private void obfuscateEventsData(PackagesTokenData packagesTokenData) {
final int eventSize = events.size();
for (int i = 0; i < eventSize; i++) {
final Event event = events.get(i);
if (event == null) {
continue;
}
final int packageToken = packagesTokenData.getPackageTokenOrAdd(event.mPackage);
event.mPackageToken = packageToken;
if (!TextUtils.isEmpty(event.mClass)) {
event.mClassToken = packagesTokenData.getTokenOrAdd(packageToken,
event.mPackage, event.mClass);
}
if (!TextUtils.isEmpty(event.mTaskRootPackage)) {
event.mTaskRootPackageToken = packagesTokenData.getTokenOrAdd(packageToken,
event.mPackage, event.mTaskRootPackage);
}
if (!TextUtils.isEmpty(event.mTaskRootClass)) {
event.mTaskRootClassToken = packagesTokenData.getTokenOrAdd(packageToken,
event.mPackage, event.mTaskRootClass);
}
switch (event.mEventType) {
case SHORTCUT_INVOCATION:
if (!TextUtils.isEmpty(event.mShortcutId)) {
event.mShortcutIdToken = packagesTokenData.getTokenOrAdd(packageToken,
event.mPackage, event.mShortcutId);
}
break;
case NOTIFICATION_INTERRUPTION:
if (!TextUtils.isEmpty(event.mNotificationChannelId)) {
event.mNotificationChannelIdToken = packagesTokenData.getTokenOrAdd(
packageToken, event.mPackage, event.mNotificationChannelId);
}
break;
}
}
}
/**
* Obfuscates the data in this instance of interval stats.
*
* @hide
*/
public void obfuscateData(PackagesTokenData packagesTokenData) {
obfuscateUsageStatsData(packagesTokenData);
obfuscateEventsData(packagesTokenData);
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.usage;
import android.util.ArrayMap;
import android.util.SparseArray;
import java.util.ArrayList;
/**
* An object holding data defining the obfuscated packages and their token mappings.
* Used by {@link UsageStatsDatabase}.
*
* @hide
*/
public final class PackagesTokenData {
/**
* The default token for any string that hasn't been tokenized yet.
*/
public static final int UNASSIGNED_TOKEN = -1;
/**
* The package name is always stored at index 0 in {@code tokensToPackagesMap}.
*/
public static final int PACKAGE_NAME_INDEX = 0;
/**
* The main token counter for each package.
*/
public int counter = 1;
/**
* Stores a hierarchy of token to string mappings for each package, indexed by the main
* package token. The 0th index within the array list will always hold the package name.
*/
public final SparseArray<ArrayList<String>> tokensToPackagesMap = new SparseArray<>();
/**
* Stores a hierarchy of strings to token mappings for each package. This is simply an inverse
* map of the {@code tokenToPackagesMap} in this class, mainly for an O(1) access to the tokens.
*/
public final ArrayMap<String, ArrayMap<String, Integer>> packagesToTokensMap = new ArrayMap<>();
public PackagesTokenData() {
}
/**
* Fetches the token mapped to the given package name. If there is no mapping, a new token is
* created and the relevant mappings are updated.
*
* @param packageName the package name whose token is being fetched
* @return the mapped token
*/
public int getPackageTokenOrAdd(String packageName) {
ArrayMap<String, Integer> packageTokensMap = packagesToTokensMap.get(packageName);
if (packageTokensMap == null) {
packageTokensMap = new ArrayMap<>();
packagesToTokensMap.put(packageName, packageTokensMap);
}
int token = packageTokensMap.getOrDefault(packageName, UNASSIGNED_TOKEN);
if (token == UNASSIGNED_TOKEN) {
token = counter++;
// package name should always be at index 0 in the sub-mapping
ArrayList<String> tokenPackages = new ArrayList<>();
tokenPackages.add(packageName);
packageTokensMap.put(packageName, token);
tokensToPackagesMap.put(token, tokenPackages);
}
return token;
}
/**
* Fetches the token mapped to the given key within the package's context. If there is no
* mapping, a new token is created and the relevant mappings are updated.
*
* @param packageToken the package token for which the given key belongs to
* @param packageName the package name for which the given key belongs to
* @param key the key whose token is being fetched
* @return the mapped token
*/
public int getTokenOrAdd(int packageToken, String packageName, String key) {
if (packageName.equals(key)) {
return PACKAGE_NAME_INDEX;
}
int token = packagesToTokensMap.get(packageName).getOrDefault(key, UNASSIGNED_TOKEN);
if (token == UNASSIGNED_TOKEN) {
token = tokensToPackagesMap.get(packageToken).size();
packagesToTokensMap.get(packageName).put(key, token);
tokensToPackagesMap.get(packageToken).add(key);
}
return token;
}
/**
* Fetches the string represented by the given token.
*
* @param packageToken the package token for which this token belongs to
* @param token the token whose string needs to be fetched
* @return the string representing the given token
*/
public String getString(int packageToken, int token) {
return tokensToPackagesMap.get(packageToken).get(token);
}
}

View File

@@ -21,8 +21,10 @@ import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.os.Build;
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
import com.android.internal.annotations.VisibleForTesting;
@@ -75,7 +77,7 @@ import java.util.List;
* directory should be deserialized.
*/
public class UsageStatsDatabase {
private static final int DEFAULT_CURRENT_VERSION = 4;
private static final int DEFAULT_CURRENT_VERSION = 5;
/**
* Current version of the backup schema
*
@@ -93,7 +95,8 @@ public class UsageStatsDatabase {
// Persist versioned backup files.
// Should be false, except when testing new versions
static final boolean KEEP_BACKUP_DIR = false;
// STOPSHIP: b/139937606 this should be false on launch
static final boolean KEEP_BACKUP_DIR = true;
private static final String TAG = "UsageStatsDatabase";
private static final boolean DEBUG = UsageStatsService.DEBUG;
@@ -119,6 +122,11 @@ public class UsageStatsDatabase {
private boolean mFirstUpdate;
private boolean mNewUpdate;
// The obfuscated packages to tokens mappings file
private final File mPackageMappingsFile;
// Holds all of the data related to the obfuscated packages and their token mappings.
private final PackagesTokenData mPackagesTokenData = new PackagesTokenData();
/**
* UsageStatsDatabase constructor that allows setting the version number.
* This should only be used for testing.
@@ -138,6 +146,7 @@ public class UsageStatsDatabase {
mBackupsDir = new File(dir, "backups");
mUpdateBreadcrumb = new File(dir, "breadcrumb");
mSortedStatFiles = new TimeSparseArray[mIntervalDirs.length];
mPackageMappingsFile = new File(dir, "mappings");
mCal = new UnixCalendar(0);
}
@@ -150,6 +159,8 @@ public class UsageStatsDatabase {
*/
public void init(long currentTimeMillis) {
synchronized (mLock) {
readMappingsLocked();
for (File f : mIntervalDirs) {
f.mkdirs();
if (!f.exists()) {
@@ -479,6 +490,11 @@ public class UsageStatsDatabase {
private void continueUpgradeLocked(int version, long token) {
final File backupDir = new File(mBackupsDir, Long.toString(token));
// Upgrade step logic for the entire usage stats directory, not individual interval dirs.
if (version >= 5) {
readMappingsLocked();
}
// Read each file in the backup according to the version and write to the interval
// directories in the current versions format
for (int i = 0; i < mIntervalDirs.length; i++) {
@@ -494,9 +510,16 @@ public class UsageStatsDatabase {
}
try {
IntervalStats stats = new IntervalStats();
readLocked(new AtomicFile(files[j]), stats, version);
readLocked(new AtomicFile(files[j]), stats, version, mPackagesTokenData);
// Upgrade to version 5+.
// Future version upgrades should add additional logic here to upgrade.
if (mCurrentVersion >= 5) {
// Create the initial obfuscated packages map.
stats.obfuscateData(mPackagesTokenData);
}
writeLocked(new AtomicFile(new File(mIntervalDirs[i],
Long.toString(stats.beginTime))), stats, mCurrentVersion);
Long.toString(stats.beginTime))), stats, mCurrentVersion,
mPackagesTokenData);
} catch (Exception e) {
// This method is called on boot, log the exception and move on
Slog.e(TAG, "Failed to upgrade backup file : " + files[j].toString());
@@ -504,6 +527,15 @@ public class UsageStatsDatabase {
}
}
}
// Upgrade step logic for the entire usage stats directory, not individual interval dirs.
if (mCurrentVersion >= 5) {
try {
writeMappingsLocked();
} catch (IOException e) {
Slog.e(TAG, "Failed to write the tokens mappings file.");
}
}
}
public void onTimeChanged(long timeDiffMillis) {
@@ -808,14 +840,14 @@ public class UsageStatsDatabase {
}
private void writeLocked(AtomicFile file, IntervalStats stats) throws IOException {
writeLocked(file, stats, mCurrentVersion);
writeLocked(file, stats, mCurrentVersion, mPackagesTokenData);
}
private static void writeLocked(AtomicFile file, IntervalStats stats, int version)
throws IOException {
private static void writeLocked(AtomicFile file, IntervalStats stats, int version,
PackagesTokenData packagesTokenData) throws IOException {
FileOutputStream fos = file.startWrite();
try {
writeLocked(fos, stats, version);
writeLocked(fos, stats, version, packagesTokenData);
file.finishWrite(fos);
fos = null;
} finally {
@@ -825,11 +857,11 @@ public class UsageStatsDatabase {
}
private void writeLocked(OutputStream out, IntervalStats stats) throws IOException {
writeLocked(out, stats, mCurrentVersion);
writeLocked(out, stats, mCurrentVersion, mPackagesTokenData);
}
private static void writeLocked(OutputStream out, IntervalStats stats, int version)
throws IOException {
private static void writeLocked(OutputStream out, IntervalStats stats, int version,
PackagesTokenData packagesTokenData) throws IOException {
switch (version) {
case 1:
case 2:
@@ -839,6 +871,10 @@ public class UsageStatsDatabase {
case 4:
UsageStatsProto.write(out, stats);
break;
case 5:
stats.obfuscateData(packagesTokenData);
UsageStatsProtoV2.write(out, stats);
break;
default:
throw new RuntimeException(
"Unhandled UsageStatsDatabase version: " + Integer.toString(version)
@@ -847,16 +883,16 @@ public class UsageStatsDatabase {
}
private void readLocked(AtomicFile file, IntervalStats statsOut) throws IOException {
readLocked(file, statsOut, mCurrentVersion);
readLocked(file, statsOut, mCurrentVersion, mPackagesTokenData);
}
private static void readLocked(AtomicFile file, IntervalStats statsOut, int version)
throws IOException {
private static void readLocked(AtomicFile file, IntervalStats statsOut, int version,
PackagesTokenData packagesTokenData) throws IOException {
try {
FileInputStream in = file.openRead();
try {
statsOut.beginTime = parseBeginTime(file);
readLocked(in, statsOut, version);
readLocked(in, statsOut, version, packagesTokenData);
statsOut.lastTimeSaved = file.getLastModifiedTime();
} finally {
try {
@@ -872,11 +908,11 @@ public class UsageStatsDatabase {
}
private void readLocked(InputStream in, IntervalStats statsOut) throws IOException {
readLocked(in, statsOut, mCurrentVersion);
readLocked(in, statsOut, mCurrentVersion, mPackagesTokenData);
}
private static void readLocked(InputStream in, IntervalStats statsOut, int version)
throws IOException {
private static void readLocked(InputStream in, IntervalStats statsOut, int version,
PackagesTokenData packagesTokenData) throws IOException {
switch (version) {
case 1:
case 2:
@@ -886,6 +922,10 @@ public class UsageStatsDatabase {
case 4:
UsageStatsProto.read(in, statsOut);
break;
case 5:
UsageStatsProtoV2.read(in, statsOut);
statsOut.deobfuscateData(packagesTokenData);
break;
default:
throw new RuntimeException(
"Unhandled UsageStatsDatabase version: " + Integer.toString(version)
@@ -894,6 +934,61 @@ public class UsageStatsDatabase {
}
/**
* Reads the obfuscated data file from disk containing the tokens to packages mappings and
* rebuilds the packages to tokens mappings based on that data.
*/
private void readMappingsLocked() {
if (!mPackageMappingsFile.exists()) {
return; // package mappings file is missing - recreate mappings on next write.
}
try (FileInputStream in = new AtomicFile(mPackageMappingsFile).openRead()) {
UsageStatsProtoV2.readObfuscatedData(in, mPackagesTokenData);
} catch (IOException e) {
Slog.e(TAG, "Failed to read the obfuscated packages mapping file.", e);
return;
}
final SparseArray<ArrayList<String>> tokensToPackagesMap =
mPackagesTokenData.tokensToPackagesMap;
final int tokensToPackagesMapSize = tokensToPackagesMap.size();
for (int i = 0; i < tokensToPackagesMapSize; i++) {
final int packageToken = tokensToPackagesMap.keyAt(i);
final ArrayList<String> tokensMap = tokensToPackagesMap.valueAt(i);
final ArrayMap<String, Integer> packageStringsMap = new ArrayMap<>();
final int tokensMapSize = tokensMap.size();
// package name will always be at index 0 but its token should not be 0
packageStringsMap.put(tokensMap.get(0), packageToken);
for (int j = 1; j < tokensMapSize; j++) {
packageStringsMap.put(tokensMap.get(j), j);
}
mPackagesTokenData.packagesToTokensMap.put(tokensMap.get(0), packageStringsMap);
}
}
void writeMappingsLocked() throws IOException {
final AtomicFile file = new AtomicFile(mPackageMappingsFile);
FileOutputStream fos = file.startWrite();
try {
UsageStatsProtoV2.writeObfuscatedData(fos, mPackagesTokenData);
file.finishWrite(fos);
fos = null;
} finally {
file.failWrite(fos);
}
}
void obfuscateCurrentStats(IntervalStats[] currentStats) {
if (mCurrentVersion < 5) {
return;
}
for (int i = 0; i < currentStats.length; i++) {
final IntervalStats stats = currentStats[i];
stats.obfuscateData(mPackagesTokenData);
}
}
/**
* Update the stats in the database. They may not be written to disk immediately.
*/
@@ -1098,7 +1193,7 @@ public class UsageStatsDatabase {
DataOutputStream out = new DataOutputStream(baos);
try {
out.writeLong(stats.beginTime);
writeLocked(out, stats, version);
writeLocked(out, stats, version, mPackagesTokenData);
} catch (Exception ioe) {
Slog.d(TAG, "Serializing IntervalStats Failed", ioe);
baos.reset();
@@ -1112,7 +1207,7 @@ public class UsageStatsDatabase {
IntervalStats stats = new IntervalStats();
try {
stats.beginTime = in.readLong();
readLocked(in, stats, version);
readLocked(in, stats, version, mPackagesTokenData);
} catch (IOException ioe) {
Slog.d(TAG, "DeSerializing IntervalStats Failed", ioe);
stats = null;
@@ -1142,13 +1237,18 @@ public class UsageStatsDatabase {
}
/**
* print total number and list of stats files for each interval type.
* @param pw
* Prints the obfuscated package mappings and a summary of the database files.
* @param pw the print writer to print to
*/
public void dump(IndentingPrintWriter pw, boolean compact) {
synchronized (mLock) {
pw.println();
pw.println("UsageStatsDatabase:");
pw.increaseIndent();
dumpMappings(pw);
pw.decreaseIndent();
pw.println("Database Summary:");
pw.increaseIndent();
for (int i = 0; i < mSortedStatFiles.length; i++) {
final TimeSparseArray<AtomicFile> files = mSortedStatFiles[i];
final int size = files.size();
@@ -1173,6 +1273,23 @@ public class UsageStatsDatabase {
}
}
void dumpMappings(IndentingPrintWriter pw) {
synchronized (mLock) {
pw.println("Obfuscated Packages Mappings:");
pw.increaseIndent();
pw.println("Counter: " + mPackagesTokenData.counter);
pw.println("Tokens Map Size: " + mPackagesTokenData.tokensToPackagesMap.size());
for (int i = 0; i < mPackagesTokenData.tokensToPackagesMap.size(); i++) {
final int packageToken = mPackagesTokenData.tokensToPackagesMap.keyAt(i);
final String packageStrings = String.join(", ",
mPackagesTokenData.tokensToPackagesMap.valueAt(i));
pw.println("Token " + packageToken + ": [" + packageStrings + "]");
}
pw.println();
pw.decreaseIndent();
}
}
IntervalStats readIntervalStatsForFile(int interval, long fileName) {
synchronized (mLock) {
final IntervalStats stats = new IntervalStats();

View File

@@ -407,7 +407,6 @@ final class UsageStatsProto {
proto.write(IntervalStatsProto.Configuration.COUNT, configStats.mActivationCount);
proto.write(IntervalStatsProto.Configuration.ACTIVE, isActive);
proto.end(token);
}
private static void writeEvent(ProtoOutputStream proto, long fieldId, final IntervalStats stats,
@@ -606,37 +605,4 @@ final class UsageStatsProto {
proto.flush();
}
// TODO: move to UsageStatsProtoV2
static void readPendingEvents(InputStream in, List<UsageEvents.Event> events)
throws IOException {
final ProtoInputStream proto = new ProtoInputStream(in);
final List<String> stringPool = new ArrayList<>();
final IntervalStats tmpStatsObj = new IntervalStats();
while (true) {
switch (proto.nextField()) {
case (int) IntervalStatsProto.PENDING_EVENTS:
loadEvent(proto, IntervalStatsProto.PENDING_EVENTS, tmpStatsObj, stringPool);
break;
case ProtoInputStream.NO_MORE_FIELDS:
final int eventCount = tmpStatsObj.events.size();
for (int i = 0; i < eventCount; i++) {
events.add(tmpStatsObj.events.get(i));
}
return;
}
}
}
// TODO: move to UsageStatsProtoV2
static void writePendingEvents(OutputStream out, List<UsageEvents.Event> events)
throws IOException {
final ProtoOutputStream proto = new ProtoOutputStream(out);
final IntervalStats tmpStatsObj = new IntervalStats();
final int eventCount = events.size();
for (int i = 0; i < eventCount; i++) {
writeEvent(proto, IntervalStatsProto.PENDING_EVENTS, tmpStatsObj, events.get(i));
}
proto.flush();
}
}

View File

@@ -0,0 +1,730 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.usage;
import android.app.usage.ConfigurationStats;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStats;
import android.content.res.Configuration;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedList;
/**
* UsageStats reader/writer V2 for Protocol Buffer format.
*/
final class UsageStatsProtoV2 {
private static final String TAG = "UsageStatsProtoV2";
// Static-only utility class.
private UsageStatsProtoV2() {}
private static UsageStats parseUsageStats(ProtoInputStream proto, final long beginTime)
throws IOException {
UsageStats stats = new UsageStats();
// Time attributes stored is an offset of the beginTime.
while (true) {
switch (proto.nextField()) {
case (int) UsageStatsObfuscatedProto.PACKAGE_TOKEN:
stats.mPackageToken = proto.readInt(
UsageStatsObfuscatedProto.PACKAGE_TOKEN) - 1;
break;
case (int) UsageStatsObfuscatedProto.LAST_TIME_ACTIVE_MS:
stats.mLastTimeUsed = beginTime + proto.readLong(
UsageStatsObfuscatedProto.LAST_TIME_ACTIVE_MS);
break;
case (int) UsageStatsObfuscatedProto.TOTAL_TIME_ACTIVE_MS:
stats.mTotalTimeInForeground = proto.readLong(
UsageStatsObfuscatedProto.TOTAL_TIME_ACTIVE_MS);
break;
case (int) UsageStatsObfuscatedProto.APP_LAUNCH_COUNT:
stats.mAppLaunchCount = proto.readInt(
UsageStatsObfuscatedProto.APP_LAUNCH_COUNT);
break;
case (int) UsageStatsObfuscatedProto.CHOOSER_ACTIONS:
final long token = proto.start(UsageStatsObfuscatedProto.CHOOSER_ACTIONS);
loadChooserCounts(proto, stats);
proto.end(token);
break;
case (int) UsageStatsObfuscatedProto.LAST_TIME_SERVICE_USED_MS:
stats.mLastTimeForegroundServiceUsed = beginTime + proto.readLong(
UsageStatsObfuscatedProto.LAST_TIME_SERVICE_USED_MS);
break;
case (int) UsageStatsObfuscatedProto.TOTAL_TIME_SERVICE_USED_MS:
stats.mTotalTimeForegroundServiceUsed = proto.readLong(
UsageStatsObfuscatedProto.TOTAL_TIME_SERVICE_USED_MS);
break;
case (int) UsageStatsObfuscatedProto.LAST_TIME_VISIBLE_MS:
stats.mLastTimeVisible = beginTime + proto.readLong(
UsageStatsObfuscatedProto.LAST_TIME_VISIBLE_MS);
break;
case (int) UsageStatsObfuscatedProto.TOTAL_TIME_VISIBLE_MS:
stats.mTotalTimeVisible = proto.readLong(
UsageStatsObfuscatedProto.TOTAL_TIME_VISIBLE_MS);
break;
case ProtoInputStream.NO_MORE_FIELDS:
// mLastTimeUsed was not read, assume default value of 0 plus beginTime
if (stats.mLastTimeUsed == 0) {
stats.mLastTimeUsed = beginTime;
}
return stats;
}
}
}
private static void loadCountAndTime(ProtoInputStream proto, long fieldId,
IntervalStats.EventTracker tracker) throws IOException {
final long token = proto.start(fieldId);
while (true) {
switch (proto.nextField()) {
case (int) IntervalStatsObfuscatedProto.CountAndTime.COUNT:
tracker.count = proto.readInt(IntervalStatsObfuscatedProto.CountAndTime.COUNT);
break;
case (int) IntervalStatsObfuscatedProto.CountAndTime.TIME_MS:
tracker.duration = proto.readLong(
IntervalStatsObfuscatedProto.CountAndTime.TIME_MS);
break;
case ProtoInputStream.NO_MORE_FIELDS:
proto.end(token);
return;
}
}
}
private static void loadChooserCounts(ProtoInputStream proto, UsageStats usageStats)
throws IOException {
int actionToken;
SparseIntArray counts;
if (proto.nextField(UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN)) {
// Fast path; this should work for most cases since the action token is written first
actionToken = proto.readInt(UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN) - 1;
counts = usageStats.mChooserCountsObfuscated.get(actionToken);
if (counts == null) {
counts = new SparseIntArray();
usageStats.mChooserCountsObfuscated.put(actionToken, counts);
}
} else {
counts = new SparseIntArray();
}
while (true) {
switch (proto.nextField()) {
case (int) UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN:
// Fast path failed for some reason, add the SparseIntArray object to usageStats
actionToken = proto.readInt(
UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN) - 1;
usageStats.mChooserCountsObfuscated.put(actionToken, counts);
break;
case (int) UsageStatsObfuscatedProto.ChooserAction.COUNTS:
final long token = proto.start(UsageStatsObfuscatedProto.ChooserAction.COUNTS);
loadCountsForAction(proto, counts);
proto.end(token);
break;
case ProtoInputStream.NO_MORE_FIELDS:
return; // if the action was never read, the loaded counts will be ignored.
}
}
}
private static void loadCountsForAction(ProtoInputStream proto, SparseIntArray counts)
throws IOException {
int categoryToken = PackagesTokenData.UNASSIGNED_TOKEN;
int count = 0;
while (true) {
switch (proto.nextField()) {
case (int) UsageStatsObfuscatedProto.ChooserAction.CategoryCount.CATEGORY_TOKEN:
categoryToken = proto.readInt(
UsageStatsObfuscatedProto.ChooserAction.CategoryCount.CATEGORY_TOKEN)
- 1;
break;
case (int) UsageStatsObfuscatedProto.ChooserAction.CategoryCount.COUNT:
count = proto.readInt(
UsageStatsObfuscatedProto.ChooserAction.CategoryCount.COUNT);
break;
case ProtoInputStream.NO_MORE_FIELDS:
if (categoryToken != PackagesTokenData.UNASSIGNED_TOKEN) {
counts.put(categoryToken, count);
}
return;
}
}
}
private static void loadConfigStats(ProtoInputStream proto, IntervalStats stats)
throws IOException {
boolean configActive = false;
final Configuration config = new Configuration();
ConfigurationStats configStats = new ConfigurationStats();
if (proto.nextField(IntervalStatsObfuscatedProto.Configuration.CONFIG)) {
// Fast path; this should work since the configuration is written first
config.readFromProto(proto, IntervalStatsObfuscatedProto.Configuration.CONFIG);
configStats = stats.getOrCreateConfigurationStats(config);
}
while (true) {
switch (proto.nextField()) {
case (int) IntervalStatsObfuscatedProto.Configuration.CONFIG:
// Fast path failed from some reason, add ConfigStats object to statsOut now
config.readFromProto(proto, IntervalStatsObfuscatedProto.Configuration.CONFIG);
final ConfigurationStats temp = stats.getOrCreateConfigurationStats(config);
temp.mLastTimeActive = configStats.mLastTimeActive;
temp.mTotalTimeActive = configStats.mTotalTimeActive;
temp.mActivationCount = configStats.mActivationCount;
configStats = temp;
break;
case (int) IntervalStatsObfuscatedProto.Configuration.LAST_TIME_ACTIVE_MS:
configStats.mLastTimeActive = stats.beginTime + proto.readLong(
IntervalStatsObfuscatedProto.Configuration.LAST_TIME_ACTIVE_MS);
break;
case (int) IntervalStatsObfuscatedProto.Configuration.TOTAL_TIME_ACTIVE_MS:
configStats.mTotalTimeActive = proto.readLong(
IntervalStatsObfuscatedProto.Configuration.TOTAL_TIME_ACTIVE_MS);
break;
case (int) IntervalStatsObfuscatedProto.Configuration.COUNT:
configStats.mActivationCount = proto.readInt(
IntervalStatsObfuscatedProto.Configuration.COUNT);
break;
case (int) IntervalStatsObfuscatedProto.Configuration.ACTIVE:
configActive = proto.readBoolean(
IntervalStatsObfuscatedProto.Configuration.ACTIVE);
break;
case ProtoInputStream.NO_MORE_FIELDS:
// mLastTimeActive was not assigned, assume default value of 0 plus beginTime
if (configStats.mLastTimeActive == 0) {
configStats.mLastTimeActive = stats.beginTime;
}
if (configActive) {
stats.activeConfiguration = configStats.mConfiguration;
}
return;
}
}
}
private static UsageEvents.Event parseEvent(ProtoInputStream proto, long beginTime)
throws IOException {
final UsageEvents.Event event = new UsageEvents.Event();
while (true) {
switch (proto.nextField()) {
case (int) EventObfuscatedProto.PACKAGE_TOKEN:
event.mPackageToken = proto.readInt(EventObfuscatedProto.PACKAGE_TOKEN) - 1;
break;
case (int) EventObfuscatedProto.CLASS_TOKEN:
event.mClassToken = proto.readInt(EventObfuscatedProto.CLASS_TOKEN) - 1;
break;
case (int) EventObfuscatedProto.TIME_MS:
event.mTimeStamp = beginTime + proto.readLong(EventObfuscatedProto.TIME_MS);
break;
case (int) EventObfuscatedProto.FLAGS:
event.mFlags = proto.readInt(EventObfuscatedProto.FLAGS);
break;
case (int) EventObfuscatedProto.TYPE:
event.mEventType = proto.readInt(EventObfuscatedProto.TYPE);
break;
case (int) EventObfuscatedProto.CONFIG:
event.mConfiguration = new Configuration();
event.mConfiguration.readFromProto(proto, EventObfuscatedProto.CONFIG);
break;
case (int) EventObfuscatedProto.SHORTCUT_ID_TOKEN:
event.mShortcutIdToken = proto.readInt(
EventObfuscatedProto.SHORTCUT_ID_TOKEN) - 1;
break;
case (int) EventObfuscatedProto.STANDBY_BUCKET:
event.mBucketAndReason = proto.readInt(EventObfuscatedProto.STANDBY_BUCKET);
break;
case (int) EventObfuscatedProto.NOTIFICATION_CHANNEL_ID_TOKEN:
event.mNotificationChannelIdToken = proto.readInt(
EventObfuscatedProto.NOTIFICATION_CHANNEL_ID_TOKEN) - 1;
break;
case (int) EventObfuscatedProto.INSTANCE_ID:
event.mInstanceId = proto.readInt(EventObfuscatedProto.INSTANCE_ID);
break;
case (int) EventObfuscatedProto.TASK_ROOT_PACKAGE_TOKEN:
event.mTaskRootPackageToken = proto.readInt(
EventObfuscatedProto.TASK_ROOT_PACKAGE_TOKEN) - 1;
break;
case (int) EventObfuscatedProto.TASK_ROOT_CLASS_TOKEN:
event.mTaskRootClassToken = proto.readInt(
EventObfuscatedProto.TASK_ROOT_CLASS_TOKEN) - 1;
break;
case ProtoInputStream.NO_MORE_FIELDS:
// timeStamp was not read, assume default value 0 plus beginTime
if (event.mTimeStamp == 0) {
event.mTimeStamp = beginTime;
}
return event.mPackageToken == PackagesTokenData.UNASSIGNED_TOKEN ? null : event;
}
}
}
private static void writeUsageStats(ProtoOutputStream proto, final long beginTime,
final UsageStats stats) throws IOException {
// Time attributes stored as an offset of the beginTime.
proto.write(UsageStatsObfuscatedProto.PACKAGE_TOKEN, stats.mPackageToken + 1);
proto.write(UsageStatsObfuscatedProto.LAST_TIME_ACTIVE_MS, stats.mLastTimeUsed - beginTime);
proto.write(UsageStatsObfuscatedProto.TOTAL_TIME_ACTIVE_MS, stats.mTotalTimeInForeground);
proto.write(UsageStatsObfuscatedProto.LAST_TIME_SERVICE_USED_MS,
stats.mLastTimeForegroundServiceUsed - beginTime);
proto.write(UsageStatsObfuscatedProto.TOTAL_TIME_SERVICE_USED_MS,
stats.mTotalTimeForegroundServiceUsed);
proto.write(UsageStatsObfuscatedProto.LAST_TIME_VISIBLE_MS,
stats.mLastTimeVisible - beginTime);
proto.write(UsageStatsObfuscatedProto.TOTAL_TIME_VISIBLE_MS, stats.mTotalTimeVisible);
proto.write(UsageStatsObfuscatedProto.APP_LAUNCH_COUNT, stats.mAppLaunchCount);
writeChooserCounts(proto, stats);
}
private static void writeCountAndTime(ProtoOutputStream proto, long fieldId, int count,
long time) throws IOException {
final long token = proto.start(fieldId);
proto.write(IntervalStatsObfuscatedProto.CountAndTime.COUNT, count);
proto.write(IntervalStatsObfuscatedProto.CountAndTime.TIME_MS, time);
proto.end(token);
}
private static void writeChooserCounts(ProtoOutputStream proto, final UsageStats stats)
throws IOException {
if (stats == null || stats.mChooserCountsObfuscated.size() == 0) {
return;
}
final int chooserCountSize = stats.mChooserCountsObfuscated.size();
for (int i = 0; i < chooserCountSize; i++) {
final int action = stats.mChooserCountsObfuscated.keyAt(i);
final SparseIntArray counts = stats.mChooserCountsObfuscated.valueAt(i);
if (counts == null || counts.size() == 0) {
continue;
}
final long token = proto.start(UsageStatsObfuscatedProto.CHOOSER_ACTIONS);
proto.write(UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN, action + 1);
writeCountsForAction(proto, counts);
proto.end(token);
}
}
private static void writeCountsForAction(ProtoOutputStream proto, SparseIntArray counts)
throws IOException {
final int countsSize = counts.size();
for (int i = 0; i < countsSize; i++) {
final int category = counts.keyAt(i);
final int count = counts.valueAt(i);
if (count <= 0) {
continue;
}
final long token = proto.start(UsageStatsObfuscatedProto.ChooserAction.COUNTS);
proto.write(UsageStatsObfuscatedProto.ChooserAction.CategoryCount.CATEGORY_TOKEN,
category + 1);
proto.write(UsageStatsObfuscatedProto.ChooserAction.CategoryCount.COUNT, count);
proto.end(token);
}
}
private static void writeConfigStats(ProtoOutputStream proto, final long statsBeginTime,
final ConfigurationStats configStats, boolean isActive) throws IOException {
configStats.mConfiguration.writeToProto(proto,
IntervalStatsObfuscatedProto.Configuration.CONFIG);
proto.write(IntervalStatsObfuscatedProto.Configuration.LAST_TIME_ACTIVE_MS,
configStats.mLastTimeActive - statsBeginTime);
proto.write(IntervalStatsObfuscatedProto.Configuration.TOTAL_TIME_ACTIVE_MS,
configStats.mTotalTimeActive);
proto.write(IntervalStatsObfuscatedProto.Configuration.COUNT, configStats.mActivationCount);
proto.write(IntervalStatsObfuscatedProto.Configuration.ACTIVE, isActive);
}
private static void writeEvent(ProtoOutputStream proto, final long statsBeginTime,
final UsageEvents.Event event) throws IOException {
proto.write(EventObfuscatedProto.PACKAGE_TOKEN, event.mPackageToken + 1);
if (event.mClassToken != PackagesTokenData.UNASSIGNED_TOKEN) {
proto.write(EventObfuscatedProto.CLASS_TOKEN, event.mClassToken + 1);
}
proto.write(EventObfuscatedProto.TIME_MS, event.mTimeStamp - statsBeginTime);
proto.write(EventObfuscatedProto.FLAGS, event.mFlags);
proto.write(EventObfuscatedProto.TYPE, event.mEventType);
proto.write(EventObfuscatedProto.INSTANCE_ID, event.mInstanceId);
if (event.mTaskRootPackageToken != PackagesTokenData.UNASSIGNED_TOKEN) {
proto.write(EventObfuscatedProto.TASK_ROOT_PACKAGE_TOKEN,
event.mTaskRootPackageToken + 1);
}
if (event.mTaskRootClassToken != PackagesTokenData.UNASSIGNED_TOKEN) {
proto.write(EventObfuscatedProto.TASK_ROOT_CLASS_TOKEN, event.mTaskRootClassToken + 1);
}
switch (event.mEventType) {
case UsageEvents.Event.CONFIGURATION_CHANGE:
if (event.mConfiguration != null) {
event.mConfiguration.writeToProto(proto, EventObfuscatedProto.CONFIG);
}
break;
case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
if (event.mBucketAndReason != 0) {
proto.write(EventObfuscatedProto.STANDBY_BUCKET, event.mBucketAndReason);
}
break;
case UsageEvents.Event.SHORTCUT_INVOCATION:
if (event.mShortcutIdToken != PackagesTokenData.UNASSIGNED_TOKEN) {
proto.write(EventObfuscatedProto.SHORTCUT_ID_TOKEN, event.mShortcutIdToken + 1);
}
break;
case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
if (event.mNotificationChannelIdToken != PackagesTokenData.UNASSIGNED_TOKEN) {
proto.write(EventObfuscatedProto.NOTIFICATION_CHANNEL_ID_TOKEN,
event.mNotificationChannelIdToken + 1);
}
break;
}
}
/**
* Populates a tokenized version of interval stats from the input stream given.
*
* @param in the input stream from which to read events.
* @param stats the interval stats object which will be populated.
*/
public static void read(InputStream in, IntervalStats stats) throws IOException {
final ProtoInputStream proto = new ProtoInputStream(in);
while (true) {
switch (proto.nextField()) {
case (int) IntervalStatsObfuscatedProto.END_TIME_MS:
stats.endTime = stats.beginTime + proto.readLong(
IntervalStatsObfuscatedProto.END_TIME_MS);
break;
case (int) IntervalStatsObfuscatedProto.MAJOR_VERSION:
stats.majorVersion = proto.readInt(IntervalStatsObfuscatedProto.MAJOR_VERSION);
break;
case (int) IntervalStatsObfuscatedProto.MINOR_VERSION:
stats.minorVersion = proto.readInt(IntervalStatsObfuscatedProto.MINOR_VERSION);
break;
case (int) IntervalStatsObfuscatedProto.INTERACTIVE:
loadCountAndTime(proto, IntervalStatsObfuscatedProto.INTERACTIVE,
stats.interactiveTracker);
break;
case (int) IntervalStatsObfuscatedProto.NON_INTERACTIVE:
loadCountAndTime(proto, IntervalStatsObfuscatedProto.NON_INTERACTIVE,
stats.nonInteractiveTracker);
break;
case (int) IntervalStatsObfuscatedProto.KEYGUARD_SHOWN:
loadCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_SHOWN,
stats.keyguardShownTracker);
break;
case (int) IntervalStatsObfuscatedProto.KEYGUARD_HIDDEN:
loadCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_HIDDEN,
stats.keyguardHiddenTracker);
break;
case (int) IntervalStatsObfuscatedProto.PACKAGES:
final long packagesToken = proto.start(IntervalStatsObfuscatedProto.PACKAGES);
UsageStats usageStats = parseUsageStats(proto, stats.beginTime);
if (usageStats.mPackageToken != PackagesTokenData.UNASSIGNED_TOKEN) {
stats.packageStatsObfuscated.put(usageStats.mPackageToken, usageStats);
}
proto.end(packagesToken);
break;
case (int) IntervalStatsObfuscatedProto.CONFIGURATIONS:
final long configsToken = proto.start(
IntervalStatsObfuscatedProto.CONFIGURATIONS);
loadConfigStats(proto, stats);
proto.end(configsToken);
break;
case (int) IntervalStatsObfuscatedProto.EVENT_LOG:
final long eventsToken = proto.start(IntervalStatsObfuscatedProto.EVENT_LOG);
UsageEvents.Event event = parseEvent(proto, stats.beginTime);
proto.end(eventsToken);
if (event != null) {
stats.events.insert(event);
}
break;
case ProtoInputStream.NO_MORE_FIELDS:
// endTime not assigned, assume default value of 0 plus beginTime
if (stats.endTime == 0) {
stats.endTime = stats.beginTime;
}
return;
}
}
}
/**
* Writes the tokenized interval stats object to a ProtoBuf file.
*
* @param out the output stream to which to write the interval stats data.
* @param stats the interval stats object to write to the proto file.
*/
public static void write(OutputStream out, IntervalStats stats) throws IOException {
final ProtoOutputStream proto = new ProtoOutputStream(out);
proto.write(IntervalStatsObfuscatedProto.END_TIME_MS, stats.endTime - stats.beginTime);
proto.write(IntervalStatsObfuscatedProto.MAJOR_VERSION, stats.majorVersion);
proto.write(IntervalStatsObfuscatedProto.MINOR_VERSION, stats.minorVersion);
writeCountAndTime(proto, IntervalStatsObfuscatedProto.INTERACTIVE,
stats.interactiveTracker.count, stats.interactiveTracker.duration);
writeCountAndTime(proto, IntervalStatsObfuscatedProto.NON_INTERACTIVE,
stats.nonInteractiveTracker.count, stats.nonInteractiveTracker.duration);
writeCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_SHOWN,
stats.keyguardShownTracker.count, stats.keyguardShownTracker.duration);
writeCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_HIDDEN,
stats.keyguardHiddenTracker.count, stats.keyguardHiddenTracker.duration);
final int statsCount = stats.packageStatsObfuscated.size();
for (int i = 0; i < statsCount; i++) {
final long token = proto.start(IntervalStatsObfuscatedProto.PACKAGES);
writeUsageStats(proto, stats.beginTime, stats.packageStatsObfuscated.valueAt(i));
proto.end(token);
}
final int configCount = stats.configurations.size();
for (int i = 0; i < configCount; i++) {
boolean active = stats.activeConfiguration.equals(stats.configurations.keyAt(i));
final long token = proto.start(IntervalStatsObfuscatedProto.CONFIGURATIONS);
writeConfigStats(proto, stats.beginTime, stats.configurations.valueAt(i), active);
proto.end(token);
}
final int eventCount = stats.events.size();
for (int i = 0; i < eventCount; i++) {
final long token = proto.start(IntervalStatsObfuscatedProto.EVENT_LOG);
writeEvent(proto, stats.beginTime, stats.events.get(i));
proto.end(token);
}
proto.flush();
}
/***** Read/Write obfuscated packages data logic. *****/
private static void loadPackagesMap(ProtoInputStream proto,
SparseArray<ArrayList<String>> tokensToPackagesMap) throws IOException {
int key = PackagesTokenData.UNASSIGNED_TOKEN;
final ArrayList<String> strings = new ArrayList<>();
while (true) {
switch (proto.nextField()) {
case (int) ObfuscatedPackagesProto.PackagesMap.PACKAGE_TOKEN:
key = proto.readInt(ObfuscatedPackagesProto.PackagesMap.PACKAGE_TOKEN) - 1;
break;
case (int) ObfuscatedPackagesProto.PackagesMap.STRINGS:
strings.add(proto.readString(ObfuscatedPackagesProto.PackagesMap.STRINGS));
break;
case ProtoInputStream.NO_MORE_FIELDS:
if (key != PackagesTokenData.UNASSIGNED_TOKEN) {
tokensToPackagesMap.put(key, strings);
}
return;
}
}
}
/**
* Populates the package mappings from the input stream given.
*
* @param in the input stream from which to read the mappings.
* @param packagesTokenData the packages data object to which the data will be read to.
*/
static void readObfuscatedData(InputStream in, PackagesTokenData packagesTokenData)
throws IOException {
final ProtoInputStream proto = new ProtoInputStream(in);
while (true) {
switch (proto.nextField()) {
case (int) ObfuscatedPackagesProto.COUNTER:
packagesTokenData.counter = proto.readInt(ObfuscatedPackagesProto.COUNTER);
break;
case (int) ObfuscatedPackagesProto.PACKAGES_MAP:
final long token = proto.start(ObfuscatedPackagesProto.PACKAGES_MAP);
loadPackagesMap(proto, packagesTokenData.tokensToPackagesMap);
proto.end(token);
break;
case ProtoInputStream.NO_MORE_FIELDS:
return;
}
}
}
/**
* Writes the packages mapping data to a ProtoBuf file.
*
* @param out the output stream to which to write the mappings.
* @param packagesTokenData the packages data object holding the data to write.
*/
static void writeObfuscatedData(OutputStream out, PackagesTokenData packagesTokenData)
throws IOException {
final ProtoOutputStream proto = new ProtoOutputStream(out);
proto.write(ObfuscatedPackagesProto.COUNTER, packagesTokenData.counter);
final int mapSize = packagesTokenData.tokensToPackagesMap.size();
for (int i = 0; i < mapSize; i++) {
final long token = proto.start(ObfuscatedPackagesProto.PACKAGES_MAP);
int packageToken = packagesTokenData.tokensToPackagesMap.keyAt(i);
proto.write(ObfuscatedPackagesProto.PackagesMap.PACKAGE_TOKEN, packageToken + 1);
final ArrayList<String> strings = packagesTokenData.tokensToPackagesMap.valueAt(i);
final int listSize = strings.size();
for (int j = 0; j < listSize; j++) {
proto.write(ObfuscatedPackagesProto.PackagesMap.STRINGS, strings.get(j));
}
proto.end(token);
}
proto.flush();
}
/***** Read/Write pending events logic. *****/
private static UsageEvents.Event parsePendingEvent(ProtoInputStream proto) throws IOException {
final UsageEvents.Event event = new UsageEvents.Event();
while (true) {
switch (proto.nextField()) {
case (int) PendingEventProto.PACKAGE_NAME:
event.mPackage = proto.readString(PendingEventProto.PACKAGE_NAME);
break;
case (int) PendingEventProto.CLASS_NAME:
event.mClass = proto.readString(PendingEventProto.CLASS_NAME);
break;
case (int) PendingEventProto.TIME_MS:
event.mTimeStamp = proto.readLong(PendingEventProto.TIME_MS);
break;
case (int) PendingEventProto.FLAGS:
event.mFlags = proto.readInt(PendingEventProto.FLAGS);
break;
case (int) PendingEventProto.TYPE:
event.mEventType = proto.readInt(PendingEventProto.TYPE);
break;
case (int) PendingEventProto.CONFIG:
event.mConfiguration = new Configuration();
event.mConfiguration.readFromProto(proto, PendingEventProto.CONFIG);
break;
case (int) PendingEventProto.SHORTCUT_ID:
event.mShortcutId = proto.readString(PendingEventProto.SHORTCUT_ID);
break;
case (int) PendingEventProto.STANDBY_BUCKET:
event.mBucketAndReason = proto.readInt(PendingEventProto.STANDBY_BUCKET);
break;
case (int) PendingEventProto.NOTIFICATION_CHANNEL_ID:
event.mNotificationChannelId = proto.readString(
PendingEventProto.NOTIFICATION_CHANNEL_ID);
break;
case (int) PendingEventProto.INSTANCE_ID:
event.mInstanceId = proto.readInt(PendingEventProto.INSTANCE_ID);
break;
case (int) PendingEventProto.TASK_ROOT_PACKAGE:
event.mTaskRootPackage = proto.readString(PendingEventProto.TASK_ROOT_PACKAGE);
break;
case (int) PendingEventProto.TASK_ROOT_CLASS:
event.mTaskRootClass = proto.readString(PendingEventProto.TASK_ROOT_CLASS);
break;
case ProtoInputStream.NO_MORE_FIELDS:
// Handle default values for certain events types
switch (event.mEventType) {
case UsageEvents.Event.CONFIGURATION_CHANGE:
if (event.mConfiguration == null) {
event.mConfiguration = new Configuration();
}
break;
case UsageEvents.Event.SHORTCUT_INVOCATION:
if (event.mShortcutId == null) {
event.mShortcutId = "";
}
break;
case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
if (event.mNotificationChannelId == null) {
event.mNotificationChannelId = "";
}
break;
}
return event.mPackage == null ? null : event;
}
}
}
/**
* Populates the list of pending events from the input stream given.
*
* @param in the input stream from which to read the pending events.
* @param events the list of pending events to populate.
*/
static void readPendingEvents(InputStream in, LinkedList<UsageEvents.Event> events)
throws IOException {
final ProtoInputStream proto = new ProtoInputStream(in);
while (true) {
switch (proto.nextField()) {
case (int) IntervalStatsObfuscatedProto.PENDING_EVENTS:
final long token = proto.start(IntervalStatsObfuscatedProto.PENDING_EVENTS);
UsageEvents.Event event = parsePendingEvent(proto);
proto.end(token);
if (event != null) {
events.add(event);
}
break;
case ProtoInputStream.NO_MORE_FIELDS:
return;
}
}
}
/**
* Writes the pending events to a ProtoBuf file.
*
* @param out the output stream to which to write the pending events.
* @param events the list of pending events.
*/
static void writePendingEvents(OutputStream out, LinkedList<UsageEvents.Event> events)
throws IOException {
final ProtoOutputStream proto = new ProtoOutputStream(out);
final int eventCount = events.size();
for (int i = 0; i < eventCount; i++) {
final long token = proto.start(IntervalStatsObfuscatedProto.PENDING_EVENTS);
final UsageEvents.Event event = events.get(i);
proto.write(PendingEventProto.PACKAGE_NAME, event.mPackage);
if (event.mClass != null) {
proto.write(PendingEventProto.CLASS_NAME, event.mClass);
}
proto.write(PendingEventProto.TIME_MS, event.mTimeStamp);
proto.write(PendingEventProto.FLAGS, event.mFlags);
proto.write(PendingEventProto.TYPE, event.mEventType);
proto.write(PendingEventProto.INSTANCE_ID, event.mInstanceId);
if (event.mTaskRootPackage != null) {
proto.write(PendingEventProto.TASK_ROOT_PACKAGE, event.mTaskRootPackage);
}
if (event.mTaskRootClass != null) {
proto.write(PendingEventProto.TASK_ROOT_CLASS, event.mTaskRootClass);
}
switch (event.mEventType) {
case UsageEvents.Event.CONFIGURATION_CHANGE:
if (event.mConfiguration != null) {
event.mConfiguration.writeToProto(proto, PendingEventProto.CONFIG);
}
break;
case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
if (event.mBucketAndReason != 0) {
proto.write(PendingEventProto.STANDBY_BUCKET, event.mBucketAndReason);
}
break;
case UsageEvents.Event.SHORTCUT_INVOCATION:
if (event.mShortcutId != null) {
proto.write(PendingEventProto.SHORTCUT_ID, event.mShortcutId);
}
break;
case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
if (event.mNotificationChannelId != null) {
proto.write(PendingEventProto.NOTIFICATION_CHANNEL_ID,
event.mNotificationChannelId);
}
break;
}
proto.end(token);
}
proto.flush();
}
}

View File

@@ -620,7 +620,7 @@ public class UsageStatsService extends SystemService implements
final AtomicFile af = new AtomicFile(pendingEventsFiles[i]);
try {
try (FileInputStream in = af.openRead()) {
UsageStatsProto.readPendingEvents(in, pendingEvents);
UsageStatsProtoV2.readPendingEvents(in, pendingEvents);
}
} catch (IOException e) {
// Even if one file read fails, exit here to keep all events in order on disk -
@@ -650,7 +650,7 @@ public class UsageStatsService extends SystemService implements
FileOutputStream fos = null;
try {
fos = af.startWrite();
UsageStatsProto.writePendingEvents(fos, pendingEvents);
UsageStatsProtoV2.writePendingEvents(fos, pendingEvents);
af.finishWrite(fos);
fos = null;
pendingEvents.clear();
@@ -1033,21 +1033,13 @@ public class UsageStatsService extends SystemService implements
ipw.decreaseIndent();
}
} else {
final int user;
try {
user = Integer.valueOf(args[i + 1]);
} catch (NumberFormatException nfe) {
ipw.println("invalid user specified.");
return;
final int user = parseUserIdFromArgs(args, i, ipw);
if (user != UserHandle.USER_NULL) {
final String[] remainingArgs = Arrays.copyOfRange(
args, i + 2, args.length);
// dump everything for the specified user
mUserState.get(user).dumpFile(ipw, remainingArgs);
}
if (mUserState.indexOfKey(user) < 0) {
ipw.println("the specified user does not exist.");
return;
}
final String[] remainingArgs = Arrays.copyOfRange(
args, i + 2, args.length);
// dump everything for the specified user
mUserState.get(user).dumpFile(ipw, remainingArgs);
}
return;
} else if ("database-info".equals(arg)) {
@@ -1062,19 +1054,11 @@ public class UsageStatsService extends SystemService implements
ipw.decreaseIndent();
}
} else {
final int user;
try {
user = Integer.valueOf(args[i + 1]);
} catch (NumberFormatException nfe) {
ipw.println("invalid user specified.");
return;
final int user = parseUserIdFromArgs(args, i, ipw);
if (user != UserHandle.USER_NULL) {
// dump info only for the specified user
mUserState.get(user).dumpDatabaseInfo(ipw);
}
if (mUserState.indexOfKey(user) < 0) {
ipw.println("the specified user does not exist.");
return;
}
// dump info only for the specified user
mUserState.get(user).dumpDatabaseInfo(ipw);
}
return;
} else if ("appstandby".equals(arg)) {
@@ -1082,15 +1066,18 @@ public class UsageStatsService extends SystemService implements
return;
} else if ("stats-directory".equals(arg)) {
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
final int userId;
try {
userId = Integer.valueOf(args[i + 1]);
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
ipw.println("invalid user specified.");
return;
final int userId = parseUserIdFromArgs(args, i, ipw);
if (userId != UserHandle.USER_NULL) {
ipw.println(new File(Environment.getDataSystemCeDirectory(userId),
"usagestats").getAbsolutePath());
}
return;
} else if ("mappings".equals(arg)) {
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
final int userId = parseUserIdFromArgs(args, i, ipw);
if (userId != UserHandle.USER_NULL) {
mUserState.get(userId).dumpMappings(ipw);
}
ipw.println(new File(Environment.getDataSystemCeDirectory(userId),
"usagestats").getAbsolutePath());
return;
} else if (arg != null && !arg.startsWith("-")) {
// Anything else that doesn't start with '-' is a pkg to filter
@@ -1129,6 +1116,21 @@ public class UsageStatsService extends SystemService implements
}
}
private int parseUserIdFromArgs(String[] args, int index, IndentingPrintWriter ipw) {
final int userId;
try {
userId = Integer.valueOf(args[index + 1]);
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
ipw.println("invalid user specified.");
return UserHandle.USER_NULL;
}
if (mUserState.indexOfKey(userId) < 0) {
ipw.println("the specified user does not exist.");
return UserHandle.USER_NULL;
}
return userId;
}
class H extends Handler {
public H(Looper looper) {
super(looper);

View File

@@ -524,6 +524,8 @@ class UserUsageStatsService {
if (mStatsChanged) {
Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
try {
mDatabase.obfuscateCurrentStats(mCurrentStats);
mDatabase.writeMappingsLocked();
for (int i = 0; i < mCurrentStats.length; i++) {
mDatabase.putUsageStats(i, mCurrentStats[i]);
}
@@ -700,6 +702,10 @@ class UserUsageStatsService {
mDatabase.dump(ipw, false);
}
void dumpMappings(IndentingPrintWriter ipw) {
mDatabase.dumpMappings(ipw);
}
void dumpFile(IndentingPrintWriter ipw, String[] args) {
if (args == null || args.length == 0) {
// dump all files for every interval for specified user

View File

@@ -30,6 +30,7 @@ import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.usage.IntervalStats;
import com.android.server.usage.PackagesTokenData;
import com.android.server.usage.UsageStatsDatabase;
import com.android.server.usage.UsageStatsDatabase.StatCombiner;
@@ -140,6 +141,37 @@ public class UsageStatsDatabasePerfTest {
}
}
private void runObfuscateStatsTest(int packageCount, int eventsPerPackage) {
final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState();
IntervalStats intervalStats = new IntervalStats();
populateIntervalStats(intervalStats, packageCount, eventsPerPackage);
long elapsedTimeNs = 0;
while (benchmarkState.keepRunning(elapsedTimeNs)) {
final long startTime = SystemClock.elapsedRealtimeNanos();
PackagesTokenData packagesTokenData = new PackagesTokenData();
intervalStats.obfuscateData(packagesTokenData);
final long endTime = SystemClock.elapsedRealtimeNanos();
elapsedTimeNs = endTime - startTime;
clearUsageStatsFiles();
}
}
private void runDeobfuscateStatsTest(int packageCount, int eventsPerPackage) {
final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState();
IntervalStats intervalStats = new IntervalStats();
populateIntervalStats(intervalStats, packageCount, eventsPerPackage);
long elapsedTimeNs = 0;
while (benchmarkState.keepRunning(elapsedTimeNs)) {
PackagesTokenData packagesTokenData = new PackagesTokenData();
intervalStats.obfuscateData(packagesTokenData);
final long startTime = SystemClock.elapsedRealtimeNanos();
intervalStats.deobfuscateData(packagesTokenData);
final long endTime = SystemClock.elapsedRealtimeNanos();
elapsedTimeNs = endTime - startTime;
clearUsageStatsFiles();
}
}
@Test
public void testQueryUsageStats_FewPkgsLightUse() throws IOException {
runQueryUsageStatsTest(FEW_PKGS, LIGHT_USE);
@@ -150,6 +182,16 @@ public class UsageStatsDatabasePerfTest {
runPutUsageStatsTest(FEW_PKGS, LIGHT_USE);
}
@Test
public void testObfuscateStats_FewPkgsLightUse() {
runObfuscateStatsTest(FEW_PKGS, LIGHT_USE);
}
@Test
public void testDeobfuscateStats_FewPkgsLightUse() {
runDeobfuscateStatsTest(FEW_PKGS, LIGHT_USE);
}
@Test
public void testQueryUsageStats_FewPkgsHeavyUse() throws IOException {
runQueryUsageStatsTest(FEW_PKGS, HEAVY_USE);
@@ -160,6 +202,16 @@ public class UsageStatsDatabasePerfTest {
runPutUsageStatsTest(FEW_PKGS, HEAVY_USE);
}
@Test
public void testObfuscateStats_FewPkgsHeavyUse() {
runObfuscateStatsTest(FEW_PKGS, HEAVY_USE);
}
@Test
public void testDeobfuscateStats_FewPkgsHeavyUse() {
runDeobfuscateStatsTest(FEW_PKGS, HEAVY_USE);
}
@Test
public void testQueryUsageStats_ManyPkgsLightUse() throws IOException {
runQueryUsageStatsTest(MANY_PKGS, LIGHT_USE);
@@ -170,6 +222,16 @@ public class UsageStatsDatabasePerfTest {
runPutUsageStatsTest(MANY_PKGS, LIGHT_USE);
}
@Test
public void testObfuscateStats_ManyPkgsLightUse() {
runObfuscateStatsTest(MANY_PKGS, LIGHT_USE);
}
@Test
public void testDeobfuscateStats_ManyPkgsLightUse() {
runDeobfuscateStatsTest(MANY_PKGS, LIGHT_USE);
}
@Test
public void testQueryUsageStats_ManyPkgsHeavyUse() throws IOException {
runQueryUsageStatsTest(MANY_PKGS, HEAVY_USE);
@@ -179,4 +241,14 @@ public class UsageStatsDatabasePerfTest {
public void testPutUsageStats_ManyPkgsHeavyUse() throws IOException {
runPutUsageStatsTest(MANY_PKGS, HEAVY_USE);
}
@Test
public void testObfuscateStats_ManyPkgsHeavyUse() {
runObfuscateStatsTest(MANY_PKGS, HEAVY_USE);
}
@Test
public void testDeobfuscateStats_ManyPkgsHeavyUse() {
runDeobfuscateStatsTest(MANY_PKGS, HEAVY_USE);
}
}