summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Treehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com> 2024-10-09 20:37:09 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-10-09 20:37:09 +0000
commitf0ff390d786b5e4fd30e82e9dc8f3dc4455fdefe (patch)
treedcc10218a75dc14aafbbc20eb0320c269102af4b
parent8356ba21e39537a0cbeb77246c085a8826720fee (diff)
parentcd8d4b7ac5e55533dc8d6eec7980321ef99a762d (diff)
Merge "Ensure listeners are alerted to notif after LE" into main
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java33
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java219
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());
}
/**