diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 235b8d445b1c8..c2e7cacbf551b 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -18,14 +18,12 @@ package android.app; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemService; import android.annotation.TestApi; import android.app.Notification.Builder; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; import android.content.pm.ParceledListSlice; import android.graphics.drawable.Icon; import android.net.Uri; @@ -33,7 +31,6 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; @@ -41,7 +38,6 @@ import android.os.ServiceManager; import android.os.StrictMode; import android.os.UserHandle; import android.provider.Settings.Global; -import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.StatusBarNotification; import android.service.notification.ZenModeConfig; import android.util.ArraySet; @@ -418,12 +414,16 @@ public class NotificationManager { * Creates a notification channel that notifications can be posted to. * * This can also be used to restore a deleted channel and to update an existing channel's - * name and description. + * name, description, and/or importance. * *

The name and description should only be changed if the locale changes * or in response to the user renaming this channel. For example, if a user has a channel * named 'John Doe' that represents messages from a 'John Doe', and 'John Doe' changes his name * to 'John Smith,' the channel can be renamed to match. + * + *

The importance of an existing channel will only be changed if the new importance is lower + * than the current value and the user has not altered any settings on this channel. + * * All other fields are ignored for channels that already exist. * * @param channel the channel to create. Note that the created channel may differ from this diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index d7b36aaa80088..8420c90ed76fa 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -567,6 +567,13 @@ public class RankingHelper implements RankingConfig { existing.setDescription(channel.getDescription()); existing.setBlockableSystem(channel.isBlockableSystem()); + // Apps are allowed to downgrade channel importance if the user has not changed any + // fields on this channel yet. + if (existing.getUserLockedFields() == 0 && + channel.getImportance() < existing.getImportance()) { + existing.setImportance(channel.getImportance()); + } + updateConfig(); return; } diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java index 8b3fb18173045..bbec98ce583d0 100644 --- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -81,7 +81,7 @@ import com.android.server.lights.LightsManager; @RunWithLooper public class NotificationManagerServiceTest extends NotificationTestCase { private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"; - private final int uid = Binder.getCallingUid(); + private final int mUid = Binder.getCallingUid(); private NotificationManagerService mNotificationManagerService; private INotificationManager mBinderService; private NotificationManagerInternal mInternalService; @@ -133,13 +133,13 @@ public class NotificationManagerServiceTest extends NotificationTestCase { // most tests assume badging is enabled Secure.putIntForUser(getContext().getContentResolver(), Secure.NOTIFICATION_BADGING, 1, - UserHandle.getUserHandleForUid(uid).getIdentifier()); + UserHandle.getUserHandleForUid(mUid).getIdentifier()); mNotificationManagerService = new TestableNotificationManagerService(mContext); // MockPackageManager - default returns ApplicationInfo with matching calling UID final ApplicationInfo applicationInfo = new ApplicationInfo(); - applicationInfo.uid = uid; + applicationInfo.uid = mUid; when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())) .thenReturn(applicationInfo); when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) @@ -151,7 +151,7 @@ public class NotificationManagerServiceTest extends NotificationTestCase { mTestableLooper = TestableLooper.get(this); mListener = mNotificationListeners.new ManagedServiceInfo( - null, new ComponentName(PKG, "test_class"), uid, true, null, 0); + null, new ComponentName(PKG, "test_class"), mUid, true, null, 0); when(mNotificationListeners.checkServiceTokenLocked(any())).thenReturn(mListener); try { mNotificationManagerService.init(mTestableLooper.getLooper(), mPackageManager, @@ -183,8 +183,8 @@ public class NotificationManagerServiceTest extends NotificationTestCase { .setGroup(groupKey) .setGroupSummary(isSummary); - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id, "tag", uid, 0, - nb.build(), new UserHandle(uid), null, 0); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id, "tag", mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); return new NotificationRecord(mContext, sbn, channel); } private NotificationRecord generateNotificationRecord(NotificationChannel channel) { @@ -202,8 +202,8 @@ public class NotificationManagerServiceTest extends NotificationTestCase { if (extender != null) { nb.extend(extender); } - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", uid, 0, - nb.build(), new UserHandle(uid), null, 0); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); return new NotificationRecord(mContext, sbn, channel); } @@ -211,17 +211,17 @@ public class NotificationManagerServiceTest extends NotificationTestCase { public void testCreateNotificationChannels_SingleChannel() throws Exception { final NotificationChannel channel = new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT); - mBinderService.createNotificationChannels("test_pkg", + mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(channel))); final NotificationChannel createdChannel = - mBinderService.getNotificationChannel("test_pkg", "id"); + mBinderService.getNotificationChannel(PKG, "id"); assertTrue(createdChannel != null); } @Test public void testCreateNotificationChannels_NullChannelThrowsException() throws Exception { try { - mBinderService.createNotificationChannels("test_pkg", + mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(null))); fail("Exception should be thrown immediately."); } catch (NullPointerException e) { @@ -235,10 +235,10 @@ public class NotificationManagerServiceTest extends NotificationTestCase { new NotificationChannel("id1", "name", NotificationManager.IMPORTANCE_DEFAULT); final NotificationChannel channel2 = new NotificationChannel("id2", "name", NotificationManager.IMPORTANCE_DEFAULT); - mBinderService.createNotificationChannels("test_pkg", + mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(channel1, channel2))); - assertTrue(mBinderService.getNotificationChannel("test_pkg", "id1") != null); - assertTrue(mBinderService.getNotificationChannel("test_pkg", "id2") != null); + assertTrue(mBinderService.getNotificationChannel(PKG, "id1") != null); + assertTrue(mBinderService.getNotificationChannel(PKG, "id2") != null); } @Test @@ -246,19 +246,60 @@ public class NotificationManagerServiceTest extends NotificationTestCase { throws Exception { final NotificationChannel channel = new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT); - mBinderService.createNotificationChannels("test_pkg", + mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(channel))); // Recreating the channel doesn't throw, but ignores importance. final NotificationChannel dupeChannel = new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_HIGH); - mBinderService.createNotificationChannels("test_pkg", + mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(dupeChannel))); final NotificationChannel createdChannel = - mBinderService.getNotificationChannel("test_pkg", "id"); + mBinderService.getNotificationChannel(PKG, "id"); assertEquals(NotificationManager.IMPORTANCE_DEFAULT, createdChannel.getImportance()); } + @Test + public void testCreateNotificationChannels_SecondCreateAllowedToDowngradeImportance() + throws Exception { + final NotificationChannel channel = + new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT); + mBinderService.createNotificationChannels(PKG, + new ParceledListSlice(Arrays.asList(channel))); + + // Recreating with a lower importance is allowed to modify the channel. + final NotificationChannel dupeChannel = + new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_LOW); + mBinderService.createNotificationChannels(PKG, + new ParceledListSlice(Arrays.asList(dupeChannel))); + final NotificationChannel createdChannel = + mBinderService.getNotificationChannel(PKG, "id"); + assertEquals(NotificationManager.IMPORTANCE_LOW, createdChannel.getImportance()); + } + + @Test + public void testCreateNotificationChannels_CannotDowngradeImportanceIfAlreadyUpdated() + throws Exception { + final NotificationChannel channel = + new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT); + mBinderService.createNotificationChannels(PKG, + new ParceledListSlice(Arrays.asList(channel))); + + // The user modifies importance directly, can no longer be changed by the app. + final NotificationChannel updatedChannel = + new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_HIGH); + mBinderService.updateNotificationChannelForPackage(PKG, mUid, updatedChannel); + + // Recreating with a lower importance leaves channel unchanged. + final NotificationChannel dupeChannel = + new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_LOW); + mBinderService.createNotificationChannels(PKG, + new ParceledListSlice(Arrays.asList(dupeChannel))); + final NotificationChannel createdChannel = + mBinderService.getNotificationChannel(PKG, "id"); + assertEquals(NotificationManager.IMPORTANCE_HIGH, createdChannel.getImportance()); + } + @Test public void testCreateNotificationChannels_IdenticalChannelsInListIgnoresSecond() throws Exception { @@ -266,10 +307,10 @@ public class NotificationManagerServiceTest extends NotificationTestCase { new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT); final NotificationChannel channel2 = new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_HIGH); - mBinderService.createNotificationChannels("test_pkg", + mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(channel1, channel2))); final NotificationChannel createdChannel = - mBinderService.getNotificationChannel("test_pkg", "id"); + mBinderService.getNotificationChannel(PKG, "id"); assertEquals(NotificationManager.IMPORTANCE_DEFAULT, createdChannel.getImportance()); } @@ -300,7 +341,7 @@ public class NotificationManagerServiceTest extends NotificationTestCase { public void testEnqueuedBlockedNotifications_blockedApp() throws Exception { when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false); - mBinderService.setNotificationsEnabledForPackage(PKG, uid, false); + mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false); final StatusBarNotification sbn = generateNotificationRecord(null).sbn; mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", @@ -381,7 +422,7 @@ public class NotificationManagerServiceTest extends NotificationTestCase { n.sbn.getId(), n.sbn.getNotification(), n.sbn.getUserId()); waitForIdle(); - mNotificationManagerService.mNotificationDelegate.onClearAll(uid, Binder.getCallingPid(), + mNotificationManagerService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(), n.getUserId()); waitForIdle(); StatusBarNotification[] notifs = @@ -607,7 +648,7 @@ public class NotificationManagerServiceTest extends NotificationTestCase { public void testCreateChannelNotifyListener() throws Exception { List associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); mNotificationManagerService.setRankingHelper(mRankingHelper); when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean())) @@ -632,7 +673,7 @@ public class NotificationManagerServiceTest extends NotificationTestCase { public void testCreateChannelGroupNotifyListener() throws Exception { List associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); mNotificationManagerService.setRankingHelper(mRankingHelper); NotificationChannelGroup group1 = new NotificationChannelGroup("a", "b"); NotificationChannelGroup group2 = new NotificationChannelGroup("n", "m"); @@ -652,7 +693,7 @@ public class NotificationManagerServiceTest extends NotificationTestCase { public void testUpdateChannelNotifyListener() throws Exception { List associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); mNotificationManagerService.setRankingHelper(mRankingHelper); mTestNotificationChannel.setLightColor(Color.CYAN); when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(), @@ -670,7 +711,7 @@ public class NotificationManagerServiceTest extends NotificationTestCase { public void testDeleteChannelNotifyListener() throws Exception { List associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); mNotificationManagerService.setRankingHelper(mRankingHelper); when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean())) @@ -686,7 +727,7 @@ public class NotificationManagerServiceTest extends NotificationTestCase { public void testDeleteChannelGroupNotifyListener() throws Exception { List associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); NotificationChannelGroup ncg = new NotificationChannelGroup("a", "b/c"); mNotificationManagerService.setRankingHelper(mRankingHelper); when(mRankingHelper.getNotificationChannelGroup(eq(ncg.getId()), eq(PKG), anyInt())) @@ -703,7 +744,7 @@ public class NotificationManagerServiceTest extends NotificationTestCase { mNotificationManagerService.setRankingHelper(mRankingHelper); List associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); mBinderService.updateNotificationChannelFromPrivilegedListener( null, PKG, Process.myUserHandle(), mTestNotificationChannel); @@ -719,7 +760,7 @@ public class NotificationManagerServiceTest extends NotificationTestCase { public void testUpdateNotificationChannelFromPrivilegedListener_noAccess() throws Exception { mNotificationManagerService.setRankingHelper(mRankingHelper); List associations = new ArrayList<>(); - when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); try { mBinderService.updateNotificationChannelFromPrivilegedListener( @@ -741,7 +782,7 @@ public class NotificationManagerServiceTest extends NotificationTestCase { mNotificationManagerService.setRankingHelper(mRankingHelper); List associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); mListener = mock(ManagedServices.ManagedServiceInfo.class); mListener.component = new ComponentName(PKG, PKG); when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false); @@ -767,7 +808,7 @@ public class NotificationManagerServiceTest extends NotificationTestCase { mNotificationManagerService.setRankingHelper(mRankingHelper); List associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); mBinderService.getNotificationChannelsFromPrivilegedListener( null, PKG, Process.myUserHandle()); @@ -780,7 +821,7 @@ public class NotificationManagerServiceTest extends NotificationTestCase { public void testGetNotificationChannelFromPrivilegedListener_noAccess() throws Exception { mNotificationManagerService.setRankingHelper(mRankingHelper); List associations = new ArrayList<>(); - when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); try { mBinderService.getNotificationChannelsFromPrivilegedListener( @@ -799,7 +840,7 @@ public class NotificationManagerServiceTest extends NotificationTestCase { mNotificationManagerService.setRankingHelper(mRankingHelper); List associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); mListener = mock(ManagedServices.ManagedServiceInfo.class); when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false); when(mNotificationListeners.checkServiceTokenLocked(any())).thenReturn(mListener); @@ -821,7 +862,7 @@ public class NotificationManagerServiceTest extends NotificationTestCase { mNotificationManagerService.setRankingHelper(mRankingHelper); List associations = new ArrayList<>(); associations.add("a"); - when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); mBinderService.getNotificationChannelGroupsFromPrivilegedListener( null, PKG, Process.myUserHandle()); @@ -833,7 +874,7 @@ public class NotificationManagerServiceTest extends NotificationTestCase { public void testGetNotificationChannelGroupsFromPrivilegedListener_noAccess() throws Exception { mNotificationManagerService.setRankingHelper(mRankingHelper); List associations = new ArrayList<>(); - when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); try { mBinderService.getNotificationChannelGroupsFromPrivilegedListener( @@ -850,7 +891,7 @@ public class NotificationManagerServiceTest extends NotificationTestCase { public void testGetNotificationChannelGroupsFromPrivilegedListener_badUser() throws Exception { mNotificationManagerService.setRankingHelper(mRankingHelper); List associations = new ArrayList<>(); - when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); mListener = mock(ManagedServices.ManagedServiceInfo.class); when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false); when(mNotificationListeners.checkServiceTokenLocked(any())).thenReturn(mListener); @@ -1020,8 +1061,8 @@ public class NotificationManagerServiceTest extends NotificationTestCase { .setColorized(true) .setFlag(Notification.FLAG_CAN_COLORIZE, true) .setSmallIcon(android.R.drawable.sym_def_app_icon); - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", uid, 0, - nb.build(), new UserHandle(uid), null, 0); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mBinderService.enqueueNotificationWithTag(PKG, PKG, null, @@ -1037,11 +1078,13 @@ public class NotificationManagerServiceTest extends NotificationTestCase { @Test public void testGetNotificationCountLocked() throws Exception { for (int i = 0; i < 20; i++) { - NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, i, null, false); + NotificationRecord r = + generateNotificationRecord(mTestNotificationChannel, i, null, false); mNotificationManagerService.addEnqueuedNotification(r); } for (int i = 0; i < 20; i++) { - NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, i, null, false); + NotificationRecord r = + generateNotificationRecord(mTestNotificationChannel, i, null, false); mNotificationManagerService.addNotification(r); } @@ -1051,19 +1094,23 @@ public class NotificationManagerServiceTest extends NotificationTestCase { .setSmallIcon(android.R.drawable.sym_def_app_icon) .build(); - StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "tag", uid, 0, - n, new UserHandle(uid), null, 0); + StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "tag", mUid, 0, + n, new UserHandle(mUid), null, 0); NotificationRecord otherPackage = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mNotificationManagerService.addEnqueuedNotification(otherPackage); mNotificationManagerService.addNotification(otherPackage); // Same notifications are enqueued as posted, everything counts b/c id and tag don't match - assertEquals(40, mNotificationManagerService.getNotificationCountLocked(PKG, new UserHandle(uid).getIdentifier(), 0, null)); - assertEquals(40, mNotificationManagerService.getNotificationCountLocked(PKG, new UserHandle(uid).getIdentifier(), 0, "tag2")); - assertEquals(2, mNotificationManagerService.getNotificationCountLocked("a", new UserHandle(uid).getIdentifier(), 0, "banana")); + assertEquals(40, mNotificationManagerService.getNotificationCountLocked( + PKG, new UserHandle(mUid).getIdentifier(), 0, null)); + assertEquals(40, mNotificationManagerService.getNotificationCountLocked( + PKG, new UserHandle(mUid).getIdentifier(), 0, "tag2")); + assertEquals(2, mNotificationManagerService.getNotificationCountLocked( + "a", new UserHandle(mUid).getIdentifier(), 0, "banana")); // exclude a known notification - it's excluded from only the posted list, not enqueued - assertEquals(39, mNotificationManagerService.getNotificationCountLocked(PKG, new UserHandle(uid).getIdentifier(), 0, "tag")); + assertEquals(39, mNotificationManagerService.getNotificationCountLocked( + PKG, new UserHandle(mUid).getIdentifier(), 0, "tag")); } }