Allow apps to migrate NLS filter settings
On a per user vs manifest wide basis. Test: atest, cts-verifier Bug: 184027330 Change-Id: Iae591765e410e360f289692bac012a1f26574add
This commit is contained in:
@@ -38527,6 +38527,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();
|
||||
|
||||
@@ -227,6 +227,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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4327,6 +4327,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.
|
||||
*
|
||||
@@ -5225,7 +5268,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());
|
||||
|
||||
@@ -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;
|
||||
@@ -7557,4 +7563,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)));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user