diff options
2 files changed, 232 insertions, 20 deletions
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 6c2d4f72ce57..88334ebe2abb 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -12577,18 +12577,31 @@ public class NotificationManagerService extends SystemService { // Checks if this is a request to notify system UI about a notification that // has been lifetime extended. - // (We only need to check old for the flag, because in both cancellation and - // update cases, old should have the flag, whereas in update cases the - // new will NOT have the flag.) - // If it is such a request, and this is system UI, we send the post request - // only to System UI, and break as we don't need to continue checking other - // Managed Services. - if (info.isSystemUi() && old != null && old.getNotification() != null + // We check both old and new for the flag, to avoid catching updates + // (where new will not have the flag). + // If it is such a request, and this is the system UI listener, we send + // the post request. If it's any other listener, we skip it. + if (old != null && old.getNotification() != null && (old.getNotification().flags + & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0 + && sbn != null && sbn.getNotification() != null + && (sbn.getNotification().flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) { - final NotificationRankingUpdate update = makeRankingUpdateLocked(info); - listenerCalls.add(() -> notifyPosted(info, sbnToPost, update)); - break; + if (info.isSystemUi()) { + final NotificationRankingUpdate update = + makeRankingUpdateLocked(info); + listenerCalls.add(() -> notifyPosted(info, sbnToPost, update)); + break; + } else { + // Skipping because this is the direct-reply "update" and we only + // need to send it to sysui, so we immediately continue, before it + // can get sent to other listeners below. + if (DBG) { + Slog.d(TAG, "prepareNotifyPostedLocked: direct reply update, " + + "skipping post to " + info.toString()); + } + continue; + } } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java index 983e694a8f1a..b34b1fb39a7f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java @@ -835,7 +835,7 @@ public class NotificationListenersTest extends UiServiceTestCase { } @Test - public void testListenerPost_UpdateLifetimeExtended() throws Exception { + public void testListenerPostLifetimeExtended_UpdatesOnlySysui() throws Exception { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); // Create original notification, with FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY. @@ -856,31 +856,51 @@ public class NotificationListenersTest extends UiServiceTestCase { Notification.Builder nb2 = new Notification.Builder(mContext, channel.getId()) .setContentTitle("new title") .setSmallIcon(android.R.drawable.sym_def_app_icon) - .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, false); + .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true); StatusBarNotification sbn2 = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0, nb2.build(), userHandle, null, 0); NotificationRecord toPost = new NotificationRecord(mContext, sbn2, channel); // Create system ui-like service. - ManagedServices.ManagedServiceInfo info = mListeners.new ManagedServiceInfo( + ManagedServices.ManagedServiceInfo sysuiInfo = mListeners.new ManagedServiceInfo( null, new ComponentName("a", "a"), sbn2.getUserId(), false, null, 33, 33); - info.isSystemUi = true; - INotificationListener l1 = mock(INotificationListener.class); - info.service = l1; - List<ManagedServices.ManagedServiceInfo> services = ImmutableList.of(info); + sysuiInfo.isSystemUi = true; + INotificationListener sysuiListener = mock(INotificationListener.class); + sysuiInfo.service = sysuiListener; + + // Create two non-system ui-like services. + ManagedServices.ManagedServiceInfo otherInfo1 = mListeners.new ManagedServiceInfo( + null, new ComponentName("b", "b"), sbn2.getUserId(), false, null, 33, 33); + otherInfo1.isSystemUi = false; + INotificationListener otherListener1 = mock(INotificationListener.class); + otherInfo1.service = otherListener1; + + ManagedServices.ManagedServiceInfo otherInfo2 = mListeners.new ManagedServiceInfo( + null, new ComponentName("c", "c"), sbn2.getUserId(), false, null, 33, 33); + otherInfo2.isSystemUi = false; + INotificationListener otherListener2 = mock(INotificationListener.class); + otherInfo2.service = otherListener2; + + List<ManagedServices.ManagedServiceInfo> services = ImmutableList.of(otherInfo1, sysuiInfo, + otherInfo2); when(mListeners.getServices()).thenReturn(services); FieldSetter.setField(mNm, NotificationManagerService.class.getDeclaredField("mHandler"), mock(NotificationManagerService.WorkerHandler.class)); doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any()); - doReturn(mock(NotificationRankingUpdate.class)).when(mNm).makeRankingUpdateLocked(info); + doReturn(mock(NotificationRankingUpdate.class)).when(mNm) + .makeRankingUpdateLocked(sysuiInfo); + doReturn(mock(NotificationRankingUpdate.class)).when(mNm) + .makeRankingUpdateLocked(otherInfo1); + doReturn(mock(NotificationRankingUpdate.class)).when(mNm) + .makeRankingUpdateLocked(otherInfo2); doReturn(false).when(mNm).isInLockDownMode(anyInt()); doNothing().when(mNm).updateUriPermissions(any(), any(), any(), anyInt()); doReturn(sbn2).when(mListeners).redactStatusBarNotification(sbn2); doReturn(sbn2).when(mListeners).redactStatusBarNotification(any()); - // The notification change is posted to the service listener. + // Post notification change to the service listeners. mListeners.notifyPostedLocked(toPost, old); // Verify that the post occcurs with the updated notification value. @@ -889,11 +909,190 @@ public class NotificationListenersTest extends UiServiceTestCase { runnableCaptor.getValue().run(); ArgumentCaptor<IStatusBarNotificationHolder> sbnCaptor = ArgumentCaptor.forClass(IStatusBarNotificationHolder.class); - verify(l1, times(1)).onNotificationPosted(sbnCaptor.capture(), any()); + verify(sysuiListener, times(1)).onNotificationPosted(sbnCaptor.capture(), any()); StatusBarNotification sbnResult = sbnCaptor.getValue().get(); assertThat(sbnResult.getNotification() .extras.getCharSequence(Notification.EXTRA_TITLE).toString()) .isEqualTo("new title"); + + verify(otherListener1, never()).onNotificationPosted(any(), any()); + verify(otherListener2, never()).onNotificationPosted(any(), any()); + } + + @Test + public void testListenerPostLifeimteExtension_postsToAppropriateListeners() throws Exception { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); + + // Create original notification, with FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY. + String pkg = "pkg"; + int uid = 9; + UserHandle userHandle = UserHandle.getUserHandleForUid(uid); + NotificationChannel channel = new NotificationChannel("id", "name", + NotificationManager.IMPORTANCE_HIGH); + Notification.Builder nb = new Notification.Builder(mContext, channel.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true); + StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0, + nb.build(), userHandle, null, 0); + NotificationRecord leRecord = new NotificationRecord(mContext, sbn, channel); + + // Creates updated notification (without FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) + Notification.Builder nb2 = new Notification.Builder(mContext, channel.getId()) + .setContentTitle("new title") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, false); + StatusBarNotification sbn2 = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0, + nb2.build(), userHandle, null, 0); + NotificationRecord nonLeRecord = new NotificationRecord(mContext, sbn2, channel); + + // Create system ui-like service. + ManagedServices.ManagedServiceInfo sysuiInfo = mListeners.new ManagedServiceInfo( + null, new ComponentName("a", "a"), sbn2.getUserId(), false, null, 33, 33); + sysuiInfo.isSystemUi = true; + INotificationListener sysuiListener = mock(INotificationListener.class); + sysuiInfo.service = sysuiListener; + + // Create two non-system ui-like services. + ManagedServices.ManagedServiceInfo otherInfo1 = mListeners.new ManagedServiceInfo( + null, new ComponentName("b", "b"), sbn2.getUserId(), false, null, 33, 33); + otherInfo1.isSystemUi = false; + INotificationListener otherListener1 = mock(INotificationListener.class); + otherInfo1.service = otherListener1; + + ManagedServices.ManagedServiceInfo otherInfo2 = mListeners.new ManagedServiceInfo( + null, new ComponentName("c", "c"), sbn2.getUserId(), false, null, 33, 33); + otherInfo2.isSystemUi = false; + INotificationListener otherListener2 = mock(INotificationListener.class); + otherInfo2.service = otherListener2; + + List<ManagedServices.ManagedServiceInfo> services = ImmutableList.of(otherInfo1, sysuiInfo, + otherInfo2); + when(mListeners.getServices()).thenReturn(services); + + FieldSetter.setField(mNm, + NotificationManagerService.class.getDeclaredField("mHandler"), + mock(NotificationManagerService.WorkerHandler.class)); + doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any()); + doReturn(mock(NotificationRankingUpdate.class)).when(mNm) + .makeRankingUpdateLocked(sysuiInfo); + doReturn(mock(NotificationRankingUpdate.class)).when(mNm) + .makeRankingUpdateLocked(otherInfo1); + doReturn(mock(NotificationRankingUpdate.class)).when(mNm) + .makeRankingUpdateLocked(otherInfo2); + doReturn(false).when(mNm).isInLockDownMode(anyInt()); + doNothing().when(mNm).updateUriPermissions(any(), any(), any(), anyInt()); + doReturn(sbn2).when(mListeners).redactStatusBarNotification(sbn2); + doReturn(sbn2).when(mListeners).redactStatusBarNotification(any()); + + // The notification change is posted to the service listener. + // NonLE to LE should never happen, as LE can't be set in an update by the app. + // So we just want to test LE to NonLE. + mListeners.notifyPostedLocked(nonLeRecord /*=toPost*/, leRecord /*=old*/); + + // Verify that the post occcurs with the updated notification value. + ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mNm.mHandler, times(3)).post(runnableCaptor.capture()); + List<Runnable> capturedRunnable = runnableCaptor.getAllValues(); + for (Runnable r : capturedRunnable) { + r.run(); + } + + ArgumentCaptor<IStatusBarNotificationHolder> sbnCaptor = + ArgumentCaptor.forClass(IStatusBarNotificationHolder.class); + verify(sysuiListener, times(1)).onNotificationPosted(sbnCaptor.capture(), any()); + StatusBarNotification sbnResult = sbnCaptor.getValue().get(); + assertThat(sbnResult.getNotification() + .extras.getCharSequence(Notification.EXTRA_TITLE).toString()) + .isEqualTo("new title"); + + verify(otherListener1, times(1)).onNotificationPosted(any(), any()); + verify(otherListener2, times(1)).onNotificationPosted(any(), any()); + } + + @Test + public void testNotifyPostedLocked_postsToAppropriateListeners() throws Exception { + // Create original notification + String pkg = "pkg"; + int uid = 9; + UserHandle userHandle = UserHandle.getUserHandleForUid(uid); + NotificationChannel channel = new NotificationChannel("id", "name", + NotificationManager.IMPORTANCE_HIGH); + Notification.Builder nb = new Notification.Builder(mContext, channel.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon); + StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0, + nb.build(), userHandle, null, 0); + NotificationRecord oldRecord = new NotificationRecord(mContext, sbn, channel); + + // Creates updated notification + Notification.Builder nb2 = new Notification.Builder(mContext, channel.getId()) + .setContentTitle("new title") + .setSmallIcon(android.R.drawable.sym_def_app_icon); + StatusBarNotification sbn2 = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0, + nb2.build(), userHandle, null, 0); + NotificationRecord newRecord = new NotificationRecord(mContext, sbn2, channel); + + // Create system ui-like service. + ManagedServices.ManagedServiceInfo sysuiInfo = mListeners.new ManagedServiceInfo( + null, new ComponentName("a", "a"), sbn2.getUserId(), false, null, 33, 33); + sysuiInfo.isSystemUi = true; + INotificationListener sysuiListener = mock(INotificationListener.class); + sysuiInfo.service = sysuiListener; + + // Create two non-system ui-like services. + ManagedServices.ManagedServiceInfo otherInfo1 = mListeners.new ManagedServiceInfo( + null, new ComponentName("b", "b"), sbn2.getUserId(), false, null, 33, 33); + otherInfo1.isSystemUi = false; + INotificationListener otherListener1 = mock(INotificationListener.class); + otherInfo1.service = otherListener1; + + ManagedServices.ManagedServiceInfo otherInfo2 = mListeners.new ManagedServiceInfo( + null, new ComponentName("c", "c"), sbn2.getUserId(), false, null, 33, 33); + otherInfo2.isSystemUi = false; + INotificationListener otherListener2 = mock(INotificationListener.class); + otherInfo2.service = otherListener2; + + List<ManagedServices.ManagedServiceInfo> services = ImmutableList.of(otherInfo1, sysuiInfo, + otherInfo2); + when(mListeners.getServices()).thenReturn(services); + + FieldSetter.setField(mNm, + NotificationManagerService.class.getDeclaredField("mHandler"), + mock(NotificationManagerService.WorkerHandler.class)); + doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any()); + doReturn(mock(NotificationRankingUpdate.class)).when(mNm) + .makeRankingUpdateLocked(sysuiInfo); + doReturn(mock(NotificationRankingUpdate.class)).when(mNm) + .makeRankingUpdateLocked(otherInfo1); + doReturn(mock(NotificationRankingUpdate.class)).when(mNm) + .makeRankingUpdateLocked(otherInfo2); + doReturn(false).when(mNm).isInLockDownMode(anyInt()); + doNothing().when(mNm).updateUriPermissions(any(), any(), any(), anyInt()); + doReturn(sbn2).when(mListeners).redactStatusBarNotification(sbn2); + doReturn(sbn2).when(mListeners).redactStatusBarNotification(any()); + + // The notification change is posted to the service listeners. + mListeners.notifyPostedLocked(newRecord, oldRecord); + + // Verify that the post occcurs with the updated notification value. + ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mNm.mHandler, times(3)).post(runnableCaptor.capture()); + List<Runnable> capturedRunnable = runnableCaptor.getAllValues(); + for (Runnable r : capturedRunnable) { + r.run(); + } + + ArgumentCaptor<IStatusBarNotificationHolder> sbnCaptor = + ArgumentCaptor.forClass(IStatusBarNotificationHolder.class); + verify(sysuiListener, times(1)).onNotificationPosted(sbnCaptor.capture(), any()); + StatusBarNotification sbnResult = sbnCaptor.getValue().get(); + assertThat(sbnResult.getNotification() + .extras.getCharSequence(Notification.EXTRA_TITLE).toString()) + .isEqualTo("new title"); + + verify(otherListener1, times(1)).onNotificationPosted(any(), any()); + verify(otherListener2, times(1)).onNotificationPosted(any(), any()); } /** |