Loading core/java/android/app/usage/UsageEvents.java +4 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,8 @@ */ package android.app.usage; import static android.appwidget.flags.Flags.FLAG_ENGAGEMENT_METRICS; import android.annotation.CurrentTimeMillisLong; import android.annotation.FlaggedApi; import android.annotation.IntDef; Loading Loading @@ -559,6 +561,8 @@ public final class UsageEvents implements Parcelable { public static class UserInteractionEventExtrasToken { public int mCategoryToken = UNASSIGNED_TOKEN; public int mActionToken = UNASSIGNED_TOKEN; @FlaggedApi(FLAG_ENGAGEMENT_METRICS) public byte[] mTokenizedExtras = null; public UserInteractionEventExtrasToken() { // Do nothing. Loading core/proto/android/server/usagestatsservice_v2.proto +1 −0 Original line number Diff line number Diff line Loading @@ -154,4 +154,5 @@ message ObfuscatedPackagesProto { message ObfuscatedUserInteractionExtrasProto { optional int32 category_token = 1; optional int32 action_token = 2; optional bytes tokenized_extras = 3; } services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java +101 −2 Original line number Diff line number Diff line Loading @@ -16,6 +16,10 @@ package com.android.server.usage; import static android.app.usage.UsageEvents.Event.MAX_EVENT_TYPE; import static android.app.usage.UsageEvents.Event.USER_INTERACTION; import static android.appwidget.flags.Flags.FLAG_ENGAGEMENT_METRICS; import static com.android.server.usage.PackagesTokenData.UNASSIGNED_TOKEN; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; Loading @@ -25,24 +29,36 @@ import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManager; import android.content.res.Configuration; import android.os.PersistableBundle; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.util.ArrayUtils; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.io.ByteArrayInputStream; import java.lang.reflect.Field; import java.util.Arrays; import java.util.Locale; @RunWith(AndroidJUnit4.class) @SmallTest public final class IntervalStatsTests { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); private static final int NUMBER_OF_PACKAGES = 7; private static final int NUMBER_OF_EVENTS_PER_PACKAGE = 200; private static final int NUMBER_OF_EVENTS = NUMBER_OF_PACKAGES * NUMBER_OF_EVENTS_PER_PACKAGE; private static final String EXTRA_KEY_INT_ARRAY = "com.example.extra.int_array"; private static final String EXTRA_KEY_STRING = "com.example.extra.string"; private static final String EXTRA_KEY_BUNDLE = "com.example.extra.bundle"; private void populateIntervalStats(IntervalStats intervalStats) { final int timeProgression = 23; Loading Loading @@ -101,11 +117,17 @@ public final class IntervalStatsTests { case UsageEvents.Event.USER_INTERACTION: if (Flags.userInteractionTypeApi()) { // "random" user interaction extras. PersistableBundle extras = new PersistableBundle(); final PersistableBundle extras = new PersistableBundle(); extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, "fake.namespace.category" + (i % 13)); extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, "fakeaction" + (i % 13)); extras.putIntArray(EXTRA_KEY_INT_ARRAY, new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); final char[] longString = new char[256]; Arrays.fill(longString, 'a'); extras.putString(EXTRA_KEY_STRING, new String(longString)); extras.putPersistableBundle(EXTRA_KEY_BUNDLE, new PersistableBundle()); event.mExtras = extras; } break; Loading Loading @@ -167,6 +189,82 @@ public final class IntervalStatsTests { assertThat(intervalStats.packageStats.size()).isEqualTo(NUMBER_OF_PACKAGES); } @RequiresFlagsEnabled(FLAG_ENGAGEMENT_METRICS) @Test public void testObfuscation_userInteractionExtras() throws Exception { final IntervalStats intervalStats = new IntervalStats(); populateIntervalStats(intervalStats); final PackagesTokenData packagesTokenData = new PackagesTokenData(); intervalStats.obfuscateData(packagesTokenData); // validate user interaction extras for (int i = 0; i < intervalStats.events.size(); i++) { UsageEvents.Event event = intervalStats.events.get(i); if (event.mEventType != USER_INTERACTION) continue; assertThat(event.mUserInteractionExtrasToken).isNotNull(); assertThat(event.mUserInteractionExtrasToken.mActionToken).isNotEqualTo( UNASSIGNED_TOKEN); assertThat(event.mUserInteractionExtrasToken.mCategoryToken).isNotEqualTo( UNASSIGNED_TOKEN); assertThat(event.mUserInteractionExtrasToken.mTokenizedExtras).isNotNull(); PersistableBundle tokenizedExtras = PersistableBundle.readFromStream( new ByteArrayInputStream(event.mUserInteractionExtrasToken.mTokenizedExtras)); // Bundle key should be omitted from tokenized extras. assertThat(tokenizedExtras.size()).isEqualTo(2); int packageToken = packagesTokenData.getPackageTokenOrAdd(event.getPackageName(), System.currentTimeMillis()); for (String keyToken : tokenizedExtras.keySet()) { String key = packagesTokenData.getString(packageToken, Integer.parseInt(keyToken)); assertThat(key).isNotNull(); switch (key) { case EXTRA_KEY_INT_ARRAY: // int array is truncated assertThat(tokenizedExtras.getIntArray(keyToken)).hasLength(10); break; case EXTRA_KEY_STRING: // String is truncated assertThat(tokenizedExtras.getString(keyToken)).hasLength(127); break; default: throw new Exception("Unexpected key " + key + " in tokenized extras"); } } } } @RequiresFlagsEnabled(FLAG_ENGAGEMENT_METRICS) @Test public void testDeobfuscation_userInteractionExtras() throws Exception { final IntervalStats intervalStats = new IntervalStats(); populateIntervalStats(intervalStats); final PackagesTokenData packagesTokenData = new PackagesTokenData(); intervalStats.obfuscateData(packagesTokenData); intervalStats.deobfuscateData(packagesTokenData); // verify user interaction extras are present after deobfuscation for (int i = 0; i < intervalStats.events.size(); i++) { UsageEvents.Event event = intervalStats.events.get(i); if (event.mEventType != USER_INTERACTION) continue; assertThat(event.mExtras).isNotNull(); PersistableBundle extras = event.mExtras; // Bundle key is omitted assertThat(extras.keySet()).containsExactly(UsageStatsManager.EXTRA_EVENT_CATEGORY, UsageStatsManager.EXTRA_EVENT_ACTION, EXTRA_KEY_INT_ARRAY, EXTRA_KEY_STRING); int[] array = extras.getIntArray(EXTRA_KEY_INT_ARRAY); assertThat(array).hasLength(10); for (int j : array) { assertThat(array[j]).isEqualTo(j); } String string = extras.getString(EXTRA_KEY_STRING); assertThat(string).hasLength(127); for (char c : string.toCharArray()) { assertThat(c).isEqualTo('a'); } } } @Test public void testBadDataOnDeobfuscation() { final IntervalStats intervalStats = new IntervalStats(); Loading Loading @@ -214,7 +312,8 @@ public final class IntervalStatsTests { "packageStats", "configurations", "activeConfiguration", "events"}; // All fields in this list are defined in IntervalStats but not persisted private static final String[] INTERVALSTATS_IGNORED_FIELDS = {"lastTimeSaved", "packageStatsObfuscated", "CURRENT_MAJOR_VERSION", "CURRENT_MINOR_VERSION", "TAG"}; "packageStatsObfuscated", "CURRENT_MAJOR_VERSION", "CURRENT_MINOR_VERSION", "TAG", "MAX_EXTRA_ARRAY_LENGTH"}; @Test public void testIntervalStatsFieldsAreKnown() { Loading services/usage/java/com/android/server/usage/IntervalStats.java +91 −1 Original line number Diff line number Diff line Loading @@ -31,12 +31,13 @@ import static android.app.usage.UsageEvents.Event.KEYGUARD_SHOWN; import static android.app.usage.UsageEvents.Event.LOCUS_ID_SET; import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION; import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE; import static android.app.usage.UsageEvents.Event.USER_INTERACTION; import static android.app.usage.UsageEvents.Event.SCREEN_INTERACTIVE; import static android.app.usage.UsageEvents.Event.SCREEN_NON_INTERACTIVE; import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION; import static android.app.usage.UsageEvents.Event.STANDBY_BUCKET_CHANGED; import static android.app.usage.UsageEvents.Event.SYSTEM_INTERACTION; import static android.app.usage.UsageEvents.Event.USER_INTERACTION; import static android.appwidget.flags.Flags.engagementMetrics; import android.app.usage.ConfigurationStats; import android.app.usage.EventList; Loading @@ -56,12 +57,15 @@ import android.util.proto.ProtoInputStream; import com.android.internal.annotations.VisibleForTesting; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Arrays; import java.util.List; public class IntervalStats { private static final String TAG = "IntervalStats"; private static final int MAX_EXTRA_ARRAY_LENGTH = 10; public static final int CURRENT_MAJOR_VERSION = 1; public static final int CURRENT_MINOR_VERSION = 1; Loading Loading @@ -592,6 +596,7 @@ public class IntervalStats { event.mExtras = new PersistableBundle(); event.mExtras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, category); event.mExtras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, action); deobfuscateEventExtras(event, packagesTokenData, packageToken); event.mUserInteractionExtrasToken = null; } break; Loading @@ -604,6 +609,27 @@ public class IntervalStats { return dataOmitted; } private static void deobfuscateEventExtras(Event event, PackagesTokenData packagesTokenData, int packageToken) { if (!engagementMetrics() || event.mUserInteractionExtrasToken.mTokenizedExtras == null) { return; } try { final PersistableBundle tokenizedExtras = PersistableBundle.readFromStream(new ByteArrayInputStream( event.mUserInteractionExtrasToken.mTokenizedExtras)); for (String keyToken : tokenizedExtras.keySet()) { final String key = packagesTokenData.getString(packageToken, Integer.parseInt(keyToken)); if (key == null) continue; event.mExtras.putObject(key, tokenizedExtras.get(keyToken)); } } catch (IOException e) { Slog.e(TAG, "Failed to parse tokenized extras for USER_INTERACTION" + " event for " + event.mPackage, e); } } /** * Parses the obfuscated tokenized data held in this interval stats object. * Loading Loading @@ -727,6 +753,7 @@ public class IntervalStats { event.mUserInteractionExtrasToken.mActionToken = packagesTokenData.getTokenOrAdd(packageToken, event.mPackage, action); obfuscateEventsExtras(event, packagesTokenData, packageToken); } } break; Loading @@ -734,6 +761,69 @@ public class IntervalStats { } } private static void obfuscateEventsExtras(Event event, PackagesTokenData packagesTokenData, int packageToken) { if (!engagementMetrics()) return; // Tokenize other keys in the bundle if present. final PersistableBundle tokenizedExtras = new PersistableBundle(); for (String key : event.mExtras.keySet()) { if (key.equals(UsageStatsManager.EXTRA_EVENT_CATEGORY) || key.equals(UsageStatsManager.EXTRA_EVENT_ACTION)) { continue; } final int keyToken = packagesTokenData.getTokenOrAdd( packageToken, event.mPackage, key); Object value = event.mExtras.get(key); // Skip nested bundles, trim strings, and cap array length at 10. if (value == null || value instanceof PersistableBundle) { continue; } else if (value instanceof String string) { value = UsageStatsService.getTrimmedString(string); } else if (value.getClass().isArray()) { switch (value) { case short[] arr: value = Arrays.copyOf(arr, Integer.min(MAX_EXTRA_ARRAY_LENGTH, arr.length)); break; case int[] arr: value = Arrays.copyOf(arr, Integer.min(MAX_EXTRA_ARRAY_LENGTH, arr.length)); break; case long[] arr: value = Arrays.copyOf(arr, Integer.min(MAX_EXTRA_ARRAY_LENGTH, arr.length)); break; case float[] arr: value = Arrays.copyOf(arr, Integer.min(MAX_EXTRA_ARRAY_LENGTH, arr.length)); break; case double[] arr: value = Arrays.copyOf(arr, Integer.min(MAX_EXTRA_ARRAY_LENGTH, arr.length)); break; case byte[] arr: value = Arrays.copyOf(arr, Integer.min(MAX_EXTRA_ARRAY_LENGTH, arr.length)); break; case char[] arr: value = Arrays.copyOf(arr, Integer.min(MAX_EXTRA_ARRAY_LENGTH, arr.length)); break; case String[] arr: value = Arrays.copyOf(arr, Integer.min(MAX_EXTRA_ARRAY_LENGTH, arr.length)); break; default: continue; } } tokenizedExtras.putObject(String.valueOf(keyToken), value); } if (!tokenizedExtras.isEmpty()) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); try { tokenizedExtras.writeToStream(out); event.mUserInteractionExtrasToken.mTokenizedExtras = out.toByteArray(); } catch (IOException e) { Slog.e(TAG, "Failed to write tokenized extras for USER_INTERACTION event for " + event.mPackage); } } } /** * Obfuscates the data in this instance of interval stats. * @hide Loading services/usage/java/com/android/server/usage/UsageStatsProtoV2.java +12 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,8 @@ */ package com.android.server.usage; import static android.appwidget.flags.Flags.engagementMetrics; import android.app.usage.ConfigurationStats; import android.app.usage.UsageEvents; import android.app.usage.UsageEvents.Event.UserInteractionEventExtrasToken; Loading Loading @@ -931,6 +933,12 @@ final class UsageStatsProtoV2 { interactionExtrasToken.mActionToken = proto.readInt( ObfuscatedUserInteractionExtrasProto.ACTION_TOKEN) - 1; break; case (int) ObfuscatedUserInteractionExtrasProto.TOKENIZED_EXTRAS: if (engagementMetrics()) { interactionExtrasToken.mTokenizedExtras = proto.readBytes( ObfuscatedUserInteractionExtrasProto.TOKENIZED_EXTRAS); } break; case ProtoInputStream.NO_MORE_FIELDS: return interactionExtrasToken; } Loading @@ -944,6 +952,10 @@ final class UsageStatsProtoV2 { interactionExtras.mCategoryToken + 1); proto.write(ObfuscatedUserInteractionExtrasProto.ACTION_TOKEN, interactionExtras.mActionToken + 1); if (engagementMetrics()) { proto.write(ObfuscatedUserInteractionExtrasProto.TOKENIZED_EXTRAS, interactionExtras.mTokenizedExtras); } proto.end(token); } Loading Loading
core/java/android/app/usage/UsageEvents.java +4 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,8 @@ */ package android.app.usage; import static android.appwidget.flags.Flags.FLAG_ENGAGEMENT_METRICS; import android.annotation.CurrentTimeMillisLong; import android.annotation.FlaggedApi; import android.annotation.IntDef; Loading Loading @@ -559,6 +561,8 @@ public final class UsageEvents implements Parcelable { public static class UserInteractionEventExtrasToken { public int mCategoryToken = UNASSIGNED_TOKEN; public int mActionToken = UNASSIGNED_TOKEN; @FlaggedApi(FLAG_ENGAGEMENT_METRICS) public byte[] mTokenizedExtras = null; public UserInteractionEventExtrasToken() { // Do nothing. Loading
core/proto/android/server/usagestatsservice_v2.proto +1 −0 Original line number Diff line number Diff line Loading @@ -154,4 +154,5 @@ message ObfuscatedPackagesProto { message ObfuscatedUserInteractionExtrasProto { optional int32 category_token = 1; optional int32 action_token = 2; optional bytes tokenized_extras = 3; }
services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java +101 −2 Original line number Diff line number Diff line Loading @@ -16,6 +16,10 @@ package com.android.server.usage; import static android.app.usage.UsageEvents.Event.MAX_EVENT_TYPE; import static android.app.usage.UsageEvents.Event.USER_INTERACTION; import static android.appwidget.flags.Flags.FLAG_ENGAGEMENT_METRICS; import static com.android.server.usage.PackagesTokenData.UNASSIGNED_TOKEN; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; Loading @@ -25,24 +29,36 @@ import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManager; import android.content.res.Configuration; import android.os.PersistableBundle; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.util.ArrayUtils; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.io.ByteArrayInputStream; import java.lang.reflect.Field; import java.util.Arrays; import java.util.Locale; @RunWith(AndroidJUnit4.class) @SmallTest public final class IntervalStatsTests { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); private static final int NUMBER_OF_PACKAGES = 7; private static final int NUMBER_OF_EVENTS_PER_PACKAGE = 200; private static final int NUMBER_OF_EVENTS = NUMBER_OF_PACKAGES * NUMBER_OF_EVENTS_PER_PACKAGE; private static final String EXTRA_KEY_INT_ARRAY = "com.example.extra.int_array"; private static final String EXTRA_KEY_STRING = "com.example.extra.string"; private static final String EXTRA_KEY_BUNDLE = "com.example.extra.bundle"; private void populateIntervalStats(IntervalStats intervalStats) { final int timeProgression = 23; Loading Loading @@ -101,11 +117,17 @@ public final class IntervalStatsTests { case UsageEvents.Event.USER_INTERACTION: if (Flags.userInteractionTypeApi()) { // "random" user interaction extras. PersistableBundle extras = new PersistableBundle(); final PersistableBundle extras = new PersistableBundle(); extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, "fake.namespace.category" + (i % 13)); extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, "fakeaction" + (i % 13)); extras.putIntArray(EXTRA_KEY_INT_ARRAY, new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); final char[] longString = new char[256]; Arrays.fill(longString, 'a'); extras.putString(EXTRA_KEY_STRING, new String(longString)); extras.putPersistableBundle(EXTRA_KEY_BUNDLE, new PersistableBundle()); event.mExtras = extras; } break; Loading Loading @@ -167,6 +189,82 @@ public final class IntervalStatsTests { assertThat(intervalStats.packageStats.size()).isEqualTo(NUMBER_OF_PACKAGES); } @RequiresFlagsEnabled(FLAG_ENGAGEMENT_METRICS) @Test public void testObfuscation_userInteractionExtras() throws Exception { final IntervalStats intervalStats = new IntervalStats(); populateIntervalStats(intervalStats); final PackagesTokenData packagesTokenData = new PackagesTokenData(); intervalStats.obfuscateData(packagesTokenData); // validate user interaction extras for (int i = 0; i < intervalStats.events.size(); i++) { UsageEvents.Event event = intervalStats.events.get(i); if (event.mEventType != USER_INTERACTION) continue; assertThat(event.mUserInteractionExtrasToken).isNotNull(); assertThat(event.mUserInteractionExtrasToken.mActionToken).isNotEqualTo( UNASSIGNED_TOKEN); assertThat(event.mUserInteractionExtrasToken.mCategoryToken).isNotEqualTo( UNASSIGNED_TOKEN); assertThat(event.mUserInteractionExtrasToken.mTokenizedExtras).isNotNull(); PersistableBundle tokenizedExtras = PersistableBundle.readFromStream( new ByteArrayInputStream(event.mUserInteractionExtrasToken.mTokenizedExtras)); // Bundle key should be omitted from tokenized extras. assertThat(tokenizedExtras.size()).isEqualTo(2); int packageToken = packagesTokenData.getPackageTokenOrAdd(event.getPackageName(), System.currentTimeMillis()); for (String keyToken : tokenizedExtras.keySet()) { String key = packagesTokenData.getString(packageToken, Integer.parseInt(keyToken)); assertThat(key).isNotNull(); switch (key) { case EXTRA_KEY_INT_ARRAY: // int array is truncated assertThat(tokenizedExtras.getIntArray(keyToken)).hasLength(10); break; case EXTRA_KEY_STRING: // String is truncated assertThat(tokenizedExtras.getString(keyToken)).hasLength(127); break; default: throw new Exception("Unexpected key " + key + " in tokenized extras"); } } } } @RequiresFlagsEnabled(FLAG_ENGAGEMENT_METRICS) @Test public void testDeobfuscation_userInteractionExtras() throws Exception { final IntervalStats intervalStats = new IntervalStats(); populateIntervalStats(intervalStats); final PackagesTokenData packagesTokenData = new PackagesTokenData(); intervalStats.obfuscateData(packagesTokenData); intervalStats.deobfuscateData(packagesTokenData); // verify user interaction extras are present after deobfuscation for (int i = 0; i < intervalStats.events.size(); i++) { UsageEvents.Event event = intervalStats.events.get(i); if (event.mEventType != USER_INTERACTION) continue; assertThat(event.mExtras).isNotNull(); PersistableBundle extras = event.mExtras; // Bundle key is omitted assertThat(extras.keySet()).containsExactly(UsageStatsManager.EXTRA_EVENT_CATEGORY, UsageStatsManager.EXTRA_EVENT_ACTION, EXTRA_KEY_INT_ARRAY, EXTRA_KEY_STRING); int[] array = extras.getIntArray(EXTRA_KEY_INT_ARRAY); assertThat(array).hasLength(10); for (int j : array) { assertThat(array[j]).isEqualTo(j); } String string = extras.getString(EXTRA_KEY_STRING); assertThat(string).hasLength(127); for (char c : string.toCharArray()) { assertThat(c).isEqualTo('a'); } } } @Test public void testBadDataOnDeobfuscation() { final IntervalStats intervalStats = new IntervalStats(); Loading Loading @@ -214,7 +312,8 @@ public final class IntervalStatsTests { "packageStats", "configurations", "activeConfiguration", "events"}; // All fields in this list are defined in IntervalStats but not persisted private static final String[] INTERVALSTATS_IGNORED_FIELDS = {"lastTimeSaved", "packageStatsObfuscated", "CURRENT_MAJOR_VERSION", "CURRENT_MINOR_VERSION", "TAG"}; "packageStatsObfuscated", "CURRENT_MAJOR_VERSION", "CURRENT_MINOR_VERSION", "TAG", "MAX_EXTRA_ARRAY_LENGTH"}; @Test public void testIntervalStatsFieldsAreKnown() { Loading
services/usage/java/com/android/server/usage/IntervalStats.java +91 −1 Original line number Diff line number Diff line Loading @@ -31,12 +31,13 @@ import static android.app.usage.UsageEvents.Event.KEYGUARD_SHOWN; import static android.app.usage.UsageEvents.Event.LOCUS_ID_SET; import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION; import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE; import static android.app.usage.UsageEvents.Event.USER_INTERACTION; import static android.app.usage.UsageEvents.Event.SCREEN_INTERACTIVE; import static android.app.usage.UsageEvents.Event.SCREEN_NON_INTERACTIVE; import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION; import static android.app.usage.UsageEvents.Event.STANDBY_BUCKET_CHANGED; import static android.app.usage.UsageEvents.Event.SYSTEM_INTERACTION; import static android.app.usage.UsageEvents.Event.USER_INTERACTION; import static android.appwidget.flags.Flags.engagementMetrics; import android.app.usage.ConfigurationStats; import android.app.usage.EventList; Loading @@ -56,12 +57,15 @@ import android.util.proto.ProtoInputStream; import com.android.internal.annotations.VisibleForTesting; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Arrays; import java.util.List; public class IntervalStats { private static final String TAG = "IntervalStats"; private static final int MAX_EXTRA_ARRAY_LENGTH = 10; public static final int CURRENT_MAJOR_VERSION = 1; public static final int CURRENT_MINOR_VERSION = 1; Loading Loading @@ -592,6 +596,7 @@ public class IntervalStats { event.mExtras = new PersistableBundle(); event.mExtras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, category); event.mExtras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, action); deobfuscateEventExtras(event, packagesTokenData, packageToken); event.mUserInteractionExtrasToken = null; } break; Loading @@ -604,6 +609,27 @@ public class IntervalStats { return dataOmitted; } private static void deobfuscateEventExtras(Event event, PackagesTokenData packagesTokenData, int packageToken) { if (!engagementMetrics() || event.mUserInteractionExtrasToken.mTokenizedExtras == null) { return; } try { final PersistableBundle tokenizedExtras = PersistableBundle.readFromStream(new ByteArrayInputStream( event.mUserInteractionExtrasToken.mTokenizedExtras)); for (String keyToken : tokenizedExtras.keySet()) { final String key = packagesTokenData.getString(packageToken, Integer.parseInt(keyToken)); if (key == null) continue; event.mExtras.putObject(key, tokenizedExtras.get(keyToken)); } } catch (IOException e) { Slog.e(TAG, "Failed to parse tokenized extras for USER_INTERACTION" + " event for " + event.mPackage, e); } } /** * Parses the obfuscated tokenized data held in this interval stats object. * Loading Loading @@ -727,6 +753,7 @@ public class IntervalStats { event.mUserInteractionExtrasToken.mActionToken = packagesTokenData.getTokenOrAdd(packageToken, event.mPackage, action); obfuscateEventsExtras(event, packagesTokenData, packageToken); } } break; Loading @@ -734,6 +761,69 @@ public class IntervalStats { } } private static void obfuscateEventsExtras(Event event, PackagesTokenData packagesTokenData, int packageToken) { if (!engagementMetrics()) return; // Tokenize other keys in the bundle if present. final PersistableBundle tokenizedExtras = new PersistableBundle(); for (String key : event.mExtras.keySet()) { if (key.equals(UsageStatsManager.EXTRA_EVENT_CATEGORY) || key.equals(UsageStatsManager.EXTRA_EVENT_ACTION)) { continue; } final int keyToken = packagesTokenData.getTokenOrAdd( packageToken, event.mPackage, key); Object value = event.mExtras.get(key); // Skip nested bundles, trim strings, and cap array length at 10. if (value == null || value instanceof PersistableBundle) { continue; } else if (value instanceof String string) { value = UsageStatsService.getTrimmedString(string); } else if (value.getClass().isArray()) { switch (value) { case short[] arr: value = Arrays.copyOf(arr, Integer.min(MAX_EXTRA_ARRAY_LENGTH, arr.length)); break; case int[] arr: value = Arrays.copyOf(arr, Integer.min(MAX_EXTRA_ARRAY_LENGTH, arr.length)); break; case long[] arr: value = Arrays.copyOf(arr, Integer.min(MAX_EXTRA_ARRAY_LENGTH, arr.length)); break; case float[] arr: value = Arrays.copyOf(arr, Integer.min(MAX_EXTRA_ARRAY_LENGTH, arr.length)); break; case double[] arr: value = Arrays.copyOf(arr, Integer.min(MAX_EXTRA_ARRAY_LENGTH, arr.length)); break; case byte[] arr: value = Arrays.copyOf(arr, Integer.min(MAX_EXTRA_ARRAY_LENGTH, arr.length)); break; case char[] arr: value = Arrays.copyOf(arr, Integer.min(MAX_EXTRA_ARRAY_LENGTH, arr.length)); break; case String[] arr: value = Arrays.copyOf(arr, Integer.min(MAX_EXTRA_ARRAY_LENGTH, arr.length)); break; default: continue; } } tokenizedExtras.putObject(String.valueOf(keyToken), value); } if (!tokenizedExtras.isEmpty()) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); try { tokenizedExtras.writeToStream(out); event.mUserInteractionExtrasToken.mTokenizedExtras = out.toByteArray(); } catch (IOException e) { Slog.e(TAG, "Failed to write tokenized extras for USER_INTERACTION event for " + event.mPackage); } } } /** * Obfuscates the data in this instance of interval stats. * @hide Loading
services/usage/java/com/android/server/usage/UsageStatsProtoV2.java +12 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,8 @@ */ package com.android.server.usage; import static android.appwidget.flags.Flags.engagementMetrics; import android.app.usage.ConfigurationStats; import android.app.usage.UsageEvents; import android.app.usage.UsageEvents.Event.UserInteractionEventExtrasToken; Loading Loading @@ -931,6 +933,12 @@ final class UsageStatsProtoV2 { interactionExtrasToken.mActionToken = proto.readInt( ObfuscatedUserInteractionExtrasProto.ACTION_TOKEN) - 1; break; case (int) ObfuscatedUserInteractionExtrasProto.TOKENIZED_EXTRAS: if (engagementMetrics()) { interactionExtrasToken.mTokenizedExtras = proto.readBytes( ObfuscatedUserInteractionExtrasProto.TOKENIZED_EXTRAS); } break; case ProtoInputStream.NO_MORE_FIELDS: return interactionExtrasToken; } Loading @@ -944,6 +952,10 @@ final class UsageStatsProtoV2 { interactionExtras.mCategoryToken + 1); proto.write(ObfuscatedUserInteractionExtrasProto.ACTION_TOKEN, interactionExtras.mActionToken + 1); if (engagementMetrics()) { proto.write(ObfuscatedUserInteractionExtrasProto.TOKENIZED_EXTRAS, interactionExtras.mTokenizedExtras); } proto.end(token); } Loading