Obfuscate visibility of notification-related events.
UsageStats will return obfuscated NOTIFICATION_SEEN or
NOTIFICATION_INTERRUPTION events to callers of #queryEvents
and #queryEventsForUser if they don't hold the MANAGE_NOTIFICATIONS
permission.
Additionaly, refactor the query API in UsageStats to take in flags as
defined in UsageEvents to make future obfuscation/visibility parameters
cleaner.
Also, add the MANAGE_NOTIFICATIONS permission to shell for CTS test.
Bug: 144724524
Test: atest android.app.usage.cts.UsageStatsTest
Test: atest com.android.server.people.data.UsageStatsQueryHelperTest
Test: atest android.content.pm.cts.shortcutmanager.ShortcutManagerUsageTest
Change-Id: I118de7e589ac8dd5924d3740c70903fa484b79b5
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index ab71e73..0f999ad 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -41,6 +41,43 @@
/** @hide */
public static final String INSTANT_APP_CLASS_NAME = "android.instant_class";
+ /** @hide */
+ public static final String OBFUSCATED_NOTIFICATION_CHANNEL_ID = "unknown_channel_id";
+
+ /**
+ * Flag: indicates to not obfuscate or hide any usage event data when being queried.
+ * @hide
+ */
+ public static final int SHOW_ALL_EVENT_DATA = 0x00000000;
+
+ /**
+ * Flag: indicates to obfuscate package and class names for instant apps when querying usage
+ * events.
+ * @hide
+ */
+ public static final int OBFUSCATE_INSTANT_APPS = 0x00000001;
+
+ /**
+ * Flag: indicates to hide all {@link Event#SHORTCUT_INVOCATION} events when querying usage
+ * events.
+ * @hide
+ */
+ public static final int HIDE_SHORTCUT_EVENTS = 0x00000002;
+
+ /**
+ * Flag: indicates to obfuscate the notification channel id for all notification events,
+ * such as {@link Event#NOTIFICATION_SEEN} and {@link Event#NOTIFICATION_INTERRUPTION} events,
+ * when querying usage events.
+ * @hide
+ */
+ public static final int OBFUSCATE_NOTIFICATION_EVENTS = 0x00000004;
+
+ /**
+ * Flag: indicates to hide all {@link Event#LOCUS_ID_SET} events when querying usage events.
+ * @hide
+ */
+ public static final int HIDE_LOCUS_EVENTS = 0x00000008;
+
/**
* An event representing a state change for a component.
*/
@@ -627,6 +664,13 @@
return ret;
}
+ /** @hide */
+ public Event getObfuscatedNotificationEvent() {
+ final Event ret = new Event(this);
+ ret.mNotificationChannelId = OBFUSCATED_NOTIFICATION_CHANNEL_ID;
+ return ret;
+ }
+
/**
* Returns the locusId for this event if the event is of type {@link #LOCUS_ID_SET},
* otherwise it returns null.
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index af115b1..4b95e4d 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -381,6 +381,8 @@
<permission name="android.permission.REBOOT"/>
<!-- Permission required for access VIBRATOR_STATE. -->
<permission name="android.permission.ACCESS_VIBRATOR_STATE"/>
+ <!-- Permission required for UsageStatsTest CTS test. -->
+ <permission name="android.permission.MANAGE_NOTIFICATIONS"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index cc2c92b..d821050 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -253,7 +253,8 @@
<!-- Permission required for CTS test - ShortcutManagerUsageTest -->
<uses-permission android:name="android.permission.ACCESS_SHORTCUTS"/>
- <!-- Permission required for CTS test - UsageStatsTest -->
+ <!-- Permissions required for CTS test - UsageStatsTest -->
+ <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"/>
<!-- Permissions required to test ambient display. -->
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index 442c9e5..f688759 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -216,17 +216,11 @@
/**
* Returns the events for the user in the given time period.
*
- * @param obfuscateInstantApps whether instant app package names need to be obfuscated in the
- * result.
- * @param hideShortcutInvocationEvents whether the {@link UsageEvents.Event#SHORTCUT_INVOCATION}
- * events need to be excluded from the result.
- * @param hideLocusIdEvents whether the {@link UsageEvents.Event#LOCUS_ID_SET}
- * events need to be excluded from the result.
- *
+ * @param flags defines the visibility of certain usage events - see flags defined in
+ * {@link UsageEvents}.
*/
public abstract UsageEvents queryEventsForUser(@UserIdInt int userId, long beginTime,
- long endTime, boolean obfuscateInstantApps, boolean hideShortcutInvocationEvents,
- boolean hideLocusIdEvents);
+ long endTime, int flags);
/**
* Used to persist the last time a job was run for this app, in order to make decisions later
diff --git a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
index 644c155..fba4f4e 100644
--- a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
@@ -59,7 +59,7 @@
*/
boolean querySince(long sinceTime) {
UsageEvents usageEvents = mUsageStatsManagerInternal.queryEventsForUser(
- mUserId, sinceTime, System.currentTimeMillis(), false, false, false);
+ mUserId, sinceTime, System.currentTimeMillis(), UsageEvents.SHOW_ALL_EVENT_DATA);
if (usageEvents == null) {
return false;
}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
index 01d9dc0..c8543c4 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
@@ -19,9 +19,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
@@ -189,7 +189,7 @@
private void addUsageEvents(UsageEvents.Event... events) {
UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{});
when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(),
- anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(usageEvents);
+ eq(UsageEvents.SHOW_ALL_EVENT_DATA))).thenReturn(usageEvents);
}
private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 420695d..df5b311 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -500,6 +500,19 @@
== PackageManager.PERMISSION_GRANTED);
}
+ /**
+ * Obfuscate both {@link UsageEvents.Event#NOTIFICATION_SEEN} and
+ * {@link UsageEvents.Event#NOTIFICATION_INTERRUPTION} events if the provided calling uid does
+ * not hold the {@link android.Manifest.permission.MANAGE_NOTIFICATIONS} permission.
+ */
+ private boolean shouldObfuscateNotificationEvents(int callingPid, int callingUid) {
+ if (callingUid == Process.SYSTEM_UID) {
+ return false;
+ }
+ return !(getContext().checkPermission(android.Manifest.permission.MANAGE_NOTIFICATIONS,
+ callingPid, callingUid) == PackageManager.PERMISSION_GRANTED);
+ }
+
private static void deleteRecursively(File f) {
File[] files = f.listFiles();
if (files != null) {
@@ -1038,9 +1051,7 @@
/**
* Called by the Binder stub.
*/
- UsageEvents queryEvents(int userId, long beginTime, long endTime,
- boolean shouldObfuscateInstantApps, boolean shouldHideShortcutInvocationEvents,
- boolean shouldHideLocusIdEvents) {
+ UsageEvents queryEvents(int userId, long beginTime, long endTime, int flags) {
synchronized (mLock) {
if (!mUserUnlockedStates.get(userId)) {
Slog.w(TAG, "Failed to query events for locked user " + userId);
@@ -1051,8 +1062,7 @@
if (service == null) {
return null; // user was stopped or removed
}
- return service.queryEvents(beginTime, endTime, shouldObfuscateInstantApps,
- shouldHideShortcutInvocationEvents, shouldHideLocusIdEvents);
+ return service.queryEvents(beginTime, endTime, flags);
}
}
@@ -1475,10 +1485,15 @@
try {
final boolean hideShortcutInvocationEvents = shouldHideShortcutInvocationEvents(
userId, callingPackage, callingPid, callingUid);
- boolean shouldHideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid);
- return UsageStatsService.this.queryEvents(userId, beginTime, endTime,
- obfuscateInstantApps, hideShortcutInvocationEvents,
- shouldHideLocusIdEvents);
+ final boolean hideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid);
+ final boolean obfuscateNotificationEvents = shouldObfuscateNotificationEvents(
+ callingPid, callingUid);
+ int flags = UsageEvents.SHOW_ALL_EVENT_DATA;
+ if (obfuscateInstantApps) flags |= UsageEvents.OBFUSCATE_INSTANT_APPS;
+ if (hideShortcutInvocationEvents) flags |= UsageEvents.HIDE_SHORTCUT_EVENTS;
+ if (hideLocusIdEvents) flags |= UsageEvents.HIDE_LOCUS_EVENTS;
+ if (obfuscateNotificationEvents) flags |= UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS;
+ return UsageStatsService.this.queryEvents(userId, beginTime, endTime, flags);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1525,10 +1540,15 @@
try {
final boolean hideShortcutInvocationEvents = shouldHideShortcutInvocationEvents(
userId, callingPackage, callingPid, callingUid);
- boolean shouldHideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid);
- return UsageStatsService.this.queryEvents(userId, beginTime, endTime,
- obfuscateInstantApps, hideShortcutInvocationEvents,
- shouldHideLocusIdEvents);
+ final boolean obfuscateNotificationEvents = shouldObfuscateNotificationEvents(
+ callingPid, callingUid);
+ boolean hideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid);
+ int flags = UsageEvents.SHOW_ALL_EVENT_DATA;
+ if (obfuscateInstantApps) flags |= UsageEvents.OBFUSCATE_INSTANT_APPS;
+ if (hideShortcutInvocationEvents) flags |= UsageEvents.HIDE_SHORTCUT_EVENTS;
+ if (hideLocusIdEvents) flags |= UsageEvents.HIDE_LOCUS_EVENTS;
+ if (obfuscateNotificationEvents) flags |= UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS;
+ return UsageStatsService.this.queryEvents(userId, beginTime, endTime, flags);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -2144,12 +2164,8 @@
}
@Override
- public UsageEvents queryEventsForUser(int userId, long beginTime, long endTime,
- boolean obfuscateInstantApps, boolean shouldHideShortcutInvocationEvents,
- boolean shouldHideLocusIdEvents) {
- return UsageStatsService.this.queryEvents(
- userId, beginTime, endTime, obfuscateInstantApps,
- shouldHideShortcutInvocationEvents, shouldHideLocusIdEvents);
+ public UsageEvents queryEventsForUser(int userId, long beginTime, long endTime, int flags) {
+ return UsageStatsService.this.queryEvents(userId, beginTime, endTime, flags);
}
@Override
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index d9317ac..db26d88 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -18,6 +18,10 @@
import static android.app.usage.UsageEvents.Event.DEVICE_SHUTDOWN;
import static android.app.usage.UsageEvents.Event.DEVICE_STARTUP;
+import static android.app.usage.UsageEvents.HIDE_LOCUS_EVENTS;
+import static android.app.usage.UsageEvents.HIDE_SHORTCUT_EVENTS;
+import static android.app.usage.UsageEvents.OBFUSCATE_INSTANT_APPS;
+import static android.app.usage.UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS;
import static android.app.usage.UsageStatsManager.INTERVAL_BEST;
import static android.app.usage.UsageStatsManager.INTERVAL_COUNT;
import static android.app.usage.UsageStatsManager.INTERVAL_DAILY;
@@ -481,8 +485,7 @@
return queryStats(bucketType, beginTime, endTime, sEventStatsCombiner);
}
- UsageEvents queryEvents(final long beginTime, final long endTime, boolean obfuscateInstantApps,
- boolean hideShortcutInvocationEvents, boolean hideLocusIdEvents) {
+ UsageEvents queryEvents(final long beginTime, final long endTime, int flags) {
if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
return null;
}
@@ -500,15 +503,22 @@
}
Event event = stats.events.get(i);
- if (hideShortcutInvocationEvents
- && event.mEventType == Event.SHORTCUT_INVOCATION) {
+ final int eventType = event.mEventType;
+ if (eventType == Event.SHORTCUT_INVOCATION
+ && (flags & HIDE_SHORTCUT_EVENTS) == HIDE_SHORTCUT_EVENTS) {
continue;
}
- if (hideLocusIdEvents
- && event.mEventType == Event.LOCUS_ID_SET) {
+ if (eventType == Event.LOCUS_ID_SET
+ && (flags & HIDE_LOCUS_EVENTS) == HIDE_LOCUS_EVENTS) {
continue;
}
- if (obfuscateInstantApps) {
+ if ((eventType == Event.NOTIFICATION_SEEN
+ || eventType == Event.NOTIFICATION_INTERRUPTION)
+ && (flags & OBFUSCATE_NOTIFICATION_EVENTS)
+ == OBFUSCATE_NOTIFICATION_EVENTS) {
+ event = event.getObfuscatedNotificationEvent();
+ }
+ if ((flags & OBFUSCATE_INSTANT_APPS) == OBFUSCATE_INSTANT_APPS) {
event = event.getObfuscatedIfInstantApp();
}
if (event.mPackage != null) {