Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f0ff390d authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Ensure listeners are alerted to notif after LE" into main

parents 8356ba21 cd8d4b7a
Loading
Loading
Loading
Loading
+23 −10
Original line number Diff line number Diff line
@@ -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);
                            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;
                            }
                        }
                    }
+209 −10
Original line number Diff line number Diff line
@@ -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());
    }

    /**