diff options
| author | 2021-04-05 13:30:57 +0000 | |
|---|---|---|
| committer | 2021-04-05 13:30:57 +0000 | |
| commit | 47fe65234faaf32fe2450e810ead596f30e8d1df (patch) | |
| tree | 860b9d7c89b7cbb725cc18ad930b82a011074bb6 | |
| parent | 6c63ebbb9e64dbccc563eebdd079cef549d57ac8 (diff) | |
| parent | 0e36d959d0fc2dbf7bbec2d33f297b400ac01197 (diff) | |
Merge "Allow apps to migrate NLS filter settings" into sc-dev
6 files changed, 187 insertions, 5 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index c6b4ea8e803e..3d00bda551bc 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -38532,6 +38532,7 @@ package android.service.notification { method public final java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups(@NonNull String, @NonNull android.os.UserHandle); method public final java.util.List<android.app.NotificationChannel> getNotificationChannels(@NonNull String, @NonNull android.os.UserHandle); method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications(); + method public final void migrateNotificationFilter(int, @Nullable java.util.List<java.lang.String>); method public android.os.IBinder onBind(android.content.Intent); method public void onInterruptionFilterChanged(int); method public void onListenerConnected(); diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index d0e17f00e990..dab5aff5c9a8 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -228,6 +228,7 @@ interface INotificationManager NotificationListenerFilter getListenerFilter(in ComponentName cn, int userId); void setListenerFilter(in ComponentName cn, int userId, in NotificationListenerFilter nlf); + void migrateNotificationFilter(in INotificationListener token, int defaultTypes, in List<String> disallowedPkgs); void setToastRateLimitingEnabled(boolean enable); } diff --git a/core/java/android/service/notification/NotificationListenerFilter.java b/core/java/android/service/notification/NotificationListenerFilter.java index 9de75cac159a..a69d33c17e9d 100644 --- a/core/java/android/service/notification/NotificationListenerFilter.java +++ b/core/java/android/service/notification/NotificationListenerFilter.java @@ -31,15 +31,17 @@ import android.util.ArraySet; * @hide */ public class NotificationListenerFilter implements Parcelable { + + private static final int DEFAULT_TYPES = FLAG_FILTER_TYPE_CONVERSATIONS + | FLAG_FILTER_TYPE_ALERTING + | FLAG_FILTER_TYPE_SILENT + | FLAG_FILTER_TYPE_ONGOING; private int mAllowedNotificationTypes; // VersionedPackage is holding the pkg name and pkg uid private ArraySet<VersionedPackage> mDisallowedPackages; public NotificationListenerFilter() { - mAllowedNotificationTypes = FLAG_FILTER_TYPE_CONVERSATIONS - | FLAG_FILTER_TYPE_ALERTING - | FLAG_FILTER_TYPE_SILENT - | FLAG_FILTER_TYPE_ONGOING; + mAllowedNotificationTypes = DEFAULT_TYPES; mDisallowedPackages = new ArraySet<>(); } @@ -80,6 +82,10 @@ public class NotificationListenerFilter implements Parcelable { return (mAllowedNotificationTypes & type) != 0; } + public boolean areAllTypesAllowed() { + return DEFAULT_TYPES == mAllowedNotificationTypes; + } + public boolean isPackageAllowed(VersionedPackage pkg) { return !mDisallowedPackages.contains(pkg); } diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 09c3b2effcb0..6c775852fcee 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -792,6 +792,29 @@ public abstract class NotificationListenerService extends Service { } } + /** + * Lets an app migrate notification filters from its app into the OS. + * + * <p>This call will be ignored if the app has already migrated these settings or the user + * has set filters in the UI. This method is intended for user specific settings; if an app has + * already specified defaults types in its manifest with + * {@link #META_DATA_DEFAULT_FILTER_TYPES}, the defaultTypes option will be ignored.</p> + * @param defaultTypes A value representing the types of notifications that this listener should + * receive by default + * @param disallowedPkgs A list of package names whose notifications should not be seen by this + * listener, by default, because the listener does not process or display them, or because a + * user had previously disallowed these packages in the listener app's UI + */ + public final void migrateNotificationFilter(@NotificationFilterTypes int defaultTypes, + @Nullable List<String> disallowedPkgs) { + if (!isBound()) return; + try { + getNotificationInterface().migrateNotificationFilter( + mWrapper, defaultTypes, disallowedPkgs); + } catch (android.os.RemoteException ex) { + Log.v(TAG, "Unable to contact notification manager", ex); + } + } /** * Inform the notification manager that these notifications have been viewed by the diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 4ee90d6e0956..50b91766735e 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -4342,6 +4342,49 @@ public class NotificationManagerService extends SystemService { } /** + * Allows an app to set an initial notification listener filter + * + * @param token The binder for the listener, to check that the caller is allowed + */ + @Override + public void migrateNotificationFilter(INotificationListener token, int defaultTypes, + List<String> disallowedApps) { + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mNotificationLock) { + final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); + + Pair key = Pair.create(info.component, info.userid); + + NotificationListenerFilter nlf = mListeners.getNotificationListenerFilter(key); + if (nlf == null) { + nlf = new NotificationListenerFilter(); + } + if (nlf.getDisallowedPackages().isEmpty() && disallowedApps != null) { + for (String pkg : disallowedApps) { + // block the current user's version and any work profile versions + for (int userId : mUm.getProfileIds(info.userid, false)) { + try { + int uid = getUidForPackageAndUser(pkg, UserHandle.of(userId)); + VersionedPackage vp = new VersionedPackage(pkg, uid); + nlf.addPackage(vp); + } catch (Exception e) { + // pkg doesn't exist on that user; skip + } + } + } + } + if (nlf.areAllTypesAllowed()) { + nlf.setTypes(defaultTypes); + } + mListeners.setNotificationListenerFilter(key, nlf); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** * Allow an INotificationListener to simulate clearing (dismissing) a single notification. * * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onNotificationClear} @@ -5239,7 +5282,7 @@ public class NotificationManagerService extends SystemService { } private int getUidForPackageAndUser(String pkg, UserHandle user) throws RemoteException { - int uid = 0; + int uid = INVALID_UID; final long identity = Binder.clearCallingIdentity(); try { uid = mPackageManager.getPackageUid(pkg, 0, user.getIdentifier()); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 9a63782a4079..c8c8c8288bd5 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -56,6 +56,9 @@ import static android.os.UserHandle.USER_SYSTEM; import static android.service.notification.Adjustment.KEY_IMPORTANCE; import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; +import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; +import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; +import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; @@ -124,6 +127,7 @@ import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutServiceInternal; import android.content.pm.UserInfo; +import android.content.pm.VersionedPackage; import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.Icon; @@ -189,6 +193,8 @@ import com.android.server.utils.quota.MultiRateLimiter; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; +import com.google.common.collect.ImmutableList; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -7585,4 +7591,106 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), r.getSbn().getTag(), r,false); } + + @Test + public void testMigrateNotificationFilter_migrationAllAllowed() throws Exception { + int uid = 9000; + int[] userIds = new int[] {UserHandle.getUserId(mUid), 1000}; + when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds); + List<String> disallowedApps = ImmutableList.of("apples", "bananas", "cherries"); + for (int userId : userIds) { + for (String pkg : disallowedApps) { + when(mPackageManager.getPackageUid(pkg, 0, userId)).thenReturn(uid++); + } + } + + when(mListeners.getNotificationListenerFilter(any())).thenReturn( + new NotificationListenerFilter()); + + mBinderService.migrateNotificationFilter(null, + FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ONGOING, + disallowedApps); + + ArgumentCaptor<NotificationListenerFilter> captor = + ArgumentCaptor.forClass(NotificationListenerFilter.class); + verify(mListeners).setNotificationListenerFilter(any(), captor.capture()); + + assertEquals(FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ONGOING, + captor.getValue().getTypes()); + assertFalse(captor.getValue().isPackageAllowed(new VersionedPackage("apples", 9000))); + assertFalse(captor.getValue().isPackageAllowed(new VersionedPackage("cherries", 9002))); + assertFalse(captor.getValue().isPackageAllowed(new VersionedPackage("apples", 9003))); + + // hypothetical other user untouched + assertTrue(captor.getValue().isPackageAllowed(new VersionedPackage("apples", 10000))); + } + + @Test + public void testMigrateNotificationFilter_noPreexistingFilter() throws Exception { + int[] userIds = new int[] {UserHandle.getUserId(mUid)}; + when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds); + List<String> disallowedApps = ImmutableList.of("apples"); + when(mPackageManager.getPackageUid("apples", 0, UserHandle.getUserId(mUid))) + .thenReturn(1001); + + when(mListeners.getNotificationListenerFilter(any())).thenReturn(null); + + mBinderService.migrateNotificationFilter(null, FLAG_FILTER_TYPE_ONGOING, + disallowedApps); + + ArgumentCaptor<NotificationListenerFilter> captor = + ArgumentCaptor.forClass(NotificationListenerFilter.class); + verify(mListeners).setNotificationListenerFilter(any(), captor.capture()); + + assertEquals(FLAG_FILTER_TYPE_ONGOING, captor.getValue().getTypes()); + assertFalse(captor.getValue().isPackageAllowed(new VersionedPackage("apples", 1001))); + } + + @Test + public void testMigrateNotificationFilter_existingTypeFilter() throws Exception { + int[] userIds = new int[] {UserHandle.getUserId(mUid)}; + when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds); + List<String> disallowedApps = ImmutableList.of("apples"); + when(mPackageManager.getPackageUid("apples", 0, UserHandle.getUserId(mUid))) + .thenReturn(1001); + + when(mListeners.getNotificationListenerFilter(any())).thenReturn( + new NotificationListenerFilter(FLAG_FILTER_TYPE_CONVERSATIONS, new ArraySet<>())); + + mBinderService.migrateNotificationFilter(null, FLAG_FILTER_TYPE_ONGOING, + disallowedApps); + + ArgumentCaptor<NotificationListenerFilter> captor = + ArgumentCaptor.forClass(NotificationListenerFilter.class); + verify(mListeners).setNotificationListenerFilter(any(), captor.capture()); + + // type isn't saved but pkg list is + assertEquals(FLAG_FILTER_TYPE_CONVERSATIONS, captor.getValue().getTypes()); + assertFalse(captor.getValue().isPackageAllowed(new VersionedPackage("apples", 1001))); + } + + @Test + public void testMigrateNotificationFilter_existingPkgFilter() throws Exception { + int[] userIds = new int[] {UserHandle.getUserId(mUid)}; + when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds); + List<String> disallowedApps = ImmutableList.of("apples"); + when(mPackageManager.getPackageUid("apples", 0, UserHandle.getUserId(mUid))) + .thenReturn(1001); + + NotificationListenerFilter preexisting = new NotificationListenerFilter(); + preexisting.addPackage(new VersionedPackage("test", 1002)); + when(mListeners.getNotificationListenerFilter(any())).thenReturn(preexisting); + + mBinderService.migrateNotificationFilter(null, FLAG_FILTER_TYPE_ONGOING, + disallowedApps); + + ArgumentCaptor<NotificationListenerFilter> captor = + ArgumentCaptor.forClass(NotificationListenerFilter.class); + verify(mListeners).setNotificationListenerFilter(any(), captor.capture()); + + // type is saved but pkg list isn't + assertEquals(FLAG_FILTER_TYPE_ONGOING, captor.getValue().getTypes()); + assertTrue(captor.getValue().isPackageAllowed(new VersionedPackage("apples", 1001))); + assertFalse(captor.getValue().isPackageAllowed(new VersionedPackage("test", 1002))); + } } |