diff options
author | Varun Shah <varunshah@google.com> | 2019-08-27 17:11:25 -0700 |
---|---|---|
committer | Varun Shah <varunshah@google.com> | 2019-10-08 21:22:13 -0700 |
commit | e1ba9cde53546caca0befe65611bac10f7f67622 (patch) | |
tree | a98dd2e492f9fa3454851bdf96d669c4f5945c7d | |
parent | 76e56e40393ad3b14a33dbbba029e521c3850bb2 (diff) |
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
13 files changed, 1534 insertions, 95 deletions
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index 5dbca12fa3dd..4bf9c04dfade 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -312,6 +312,11 @@ 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) @@ -320,12 +325,22 @@ public final class UsageEvents implements Parcelable { /** * {@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} + */ public int mInstanceId; /** @@ -336,11 +351,21 @@ public final class UsageEvents implements Parcelable { /** * {@hide} */ + public int mTaskRootPackageToken = UNASSIGNED_TOKEN; + + /** + * {@hide} + */ public String mTaskRootClass; /** * {@hide} */ + public int mTaskRootClassToken = UNASSIGNED_TOKEN; + + /** + * {@hide} + */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public long mTimeStamp; @@ -365,6 +390,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. * {@hide} @@ -401,6 +431,11 @@ public final class UsageEvents implements Parcelable { */ public String mNotificationChannelId; + /** + * {@hide} + */ + public int mNotificationChannelIdToken = UNASSIGNED_TOKEN; + /** @hide */ @EventFlags public int mFlags; diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java index 2c021cc42cb5..9d43dd34d558 100644 --- a/core/java/android/app/usage/UsageStats.java +++ b/core/java/android/app/usage/UsageStats.java @@ -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; /** @@ -52,6 +53,11 @@ public final class UsageStats implements Parcelable { /** * {@hide} */ + public int mPackageToken = -1; + + /** + * {@hide} + */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public long mBeginTimeStamp; @@ -143,6 +149,11 @@ public final class UsageStats implements Parcelable { /** * {@hide} */ + public SparseArray<SparseIntArray> mChooserCountsObfuscated = new SparseArray<>(); + + /** + * {@hide} + */ public UsageStats() { } diff --git a/core/proto/android/server/usagestatsservice.proto b/core/proto/android/server/usagestatsservice.proto index 75f265ea6a88..f26eefad24e1 100644 --- a/core/proto/android/server/usagestatsservice.proto +++ b/core/proto/android/server/usagestatsservice.proto @@ -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 } diff --git a/core/proto/android/server/usagestatsservice_v2.proto b/core/proto/android/server/usagestatsservice_v2.proto new file mode 100644 index 000000000000..a28fcf3589f1 --- /dev/null +++ b/core/proto/android/server/usagestatsservice_v2.proto @@ -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; +} diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java index c55f459c9fe7..3a0ad4d76ef3 100644 --- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java +++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java @@ -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); + } } diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java index a783a4007da1..02e48928a7ba 100644 --- a/services/usage/java/com/android/server/usage/IntervalStats.java +++ b/services/usage/java/com/android/server/usage/IntervalStats.java @@ -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); + } } diff --git a/services/usage/java/com/android/server/usage/PackagesTokenData.java b/services/usage/java/com/android/server/usage/PackagesTokenData.java new file mode 100644 index 000000000000..8e4c639a54e4 --- /dev/null +++ b/services/usage/java/com/android/server/usage/PackagesTokenData.java @@ -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); + } +} diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java index 5197b3bad14b..d29b77c78e1c 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java +++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java @@ -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) @@ -895,6 +935,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. */ public void putUsageStats(int intervalType, IntervalStats stats) throws IOException { @@ -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(); diff --git a/services/usage/java/com/android/server/usage/UsageStatsProto.java b/services/usage/java/com/android/server/usage/UsageStatsProto.java index 6d3f416065a3..53ab230ef4a1 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsProto.java +++ b/services/usage/java/com/android/server/usage/UsageStatsProto.java @@ -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(); - } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java b/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java new file mode 100644 index 000000000000..badb3eef714e --- /dev/null +++ b/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java @@ -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(); + } +} diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 59d07357ff56..8e392a7a8456 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -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; - } - if (mUserState.indexOfKey(user) < 0) { - ipw.println("the specified user does not exist."); - 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); } - 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; - } - if (mUserState.indexOfKey(user) < 0) { - ipw.println("the specified user does not exist."); - 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); } - // 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); diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 1560b9e708e3..ec6caded01a7 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -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 diff --git a/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java index 7d9d0d52b002..62aef876a2eb 100644 --- a/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java +++ b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java @@ -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); @@ -151,6 +183,16 @@ public class UsageStatsDatabasePerfTest { } @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); } @@ -161,6 +203,16 @@ public class UsageStatsDatabasePerfTest { } @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); } @@ -171,6 +223,16 @@ public class UsageStatsDatabasePerfTest { } @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); + } } |