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

Commit 210c39d6 authored by Alexander Roederer's avatar Alexander Roederer
Browse files

Send cancelation on Lifetime Extension action

Sends a cancellation message when a lifetime extended action has an
action pressed on it. The cancellation is delayed by 200 ms to give the
app a chance to update in response.

Test: atest NotificationManagersServiceTest
Bug: 329961485
Flag: ACONFIG android.app.lifetime_extension_refactor STAGING
Change-Id: Ifed6727645b3bc5244b085ecc03220d9f32d94c2
parent 5060389b
Loading
Loading
Loading
Loading
+33 −7
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.NOT_
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR;
import static android.app.Flags.lifetimeExtensionRefactor;
import static android.app.Flags.updateRankingTime;
import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO;
import static android.app.Notification.EXTRA_LARGE_ICON_BIG;
@@ -1319,7 +1318,29 @@ public class NotificationManagerService extends SystemService {
                // Notifications that have been interacted with should no longer be lifetime
                // extended.
                if (lifetimeExtensionRefactor()) {
                    r.getSbn().getNotification().flags &= ~FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
                    // Enqueue a cancellation; this cancellation should only work if
                    // the notification still has FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY
                    // We wait for 200 milliseconds before posting the cancel, to allow the app
                    // time to update the notification in response instead.
                    // If that update goes through, the notification won't have the lifetime
                    // extended flag, and this cancellation will be dropped.
                    mHandler.scheduleCancelNotification(
                            new CancelNotificationRunnable(
                                    callingUid,
                                    callingPid,
                                    r.getSbn().getPackageName(),
                                    r.getSbn().getTag(),
                                    r.getSbn().getId(),
                                    FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY /*=mustHaveFlags*/,
                                    FLAG_NO_DISMISS /*=mustNotHaveFlags*/,
                                    false /*=sendDelete*/,
                                    r.getUserId(),
                                    REASON_APP_CANCEL,
                                    -1 /*=rank*/,
                                    -1 /*=count*/,
                                    null /*=listener*/,
                                    SystemClock.elapsedRealtime()),
                            200);
                }
            }
        }
@@ -9329,7 +9350,11 @@ public class NotificationManagerService extends SystemService {
            }
        }
        protected void scheduleCancelNotification(CancelNotificationRunnable cancelRunnable) {
        protected void scheduleCancelNotification(CancelNotificationRunnable cancelRunnable,
                                                  int delay) {
            if (lifetimeExtensionRefactor()) {
                sendMessageDelayed(Message.obtain(this, cancelRunnable), delay);
            } else {
                if (Flags.notificationReduceMessagequeueUsage()) {
                    sendMessage(Message.obtain(this, cancelRunnable));
                } else {
@@ -9338,6 +9363,7 @@ public class NotificationManagerService extends SystemService {
                    }
                }
            }
        }
        protected void scheduleOnPackageChanged(boolean removingPackage, int changeUserId,
                String[] pkgList, int[] uidList) {
@@ -9690,7 +9716,7 @@ public class NotificationManagerService extends SystemService {
        // remove notification call ends up in not removing the notification.
        mHandler.scheduleCancelNotification(new CancelNotificationRunnable(callingUid, callingPid,
                pkg, tag, id, mustHaveFlags, mustNotHaveFlags, sendDelete, userId, reason, rank,
                count, listener, SystemClock.elapsedRealtime()));
                count, listener, SystemClock.elapsedRealtime()), 0);
    }
    /**
+109 −13
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import static android.app.Notification.FLAG_CAN_COLORIZE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
import static android.app.Notification.FLAG_NO_CLEAR;
import static android.app.Notification.FLAG_NO_DISMISS;
import static android.app.Notification.FLAG_ONGOING_EVENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
import static android.app.Notification.FLAG_USER_INITIATED_JOB;
@@ -6044,7 +6045,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        waitForIdle();
        verify(handler, timeout(300).times(0)).scheduleSendRankingUpdate();
        verify(handler, times(1)).scheduleCancelNotification(any());
        verify(handler, times(1)).scheduleCancelNotification(any(), eq(0));
    }
    @Test
@@ -6303,7 +6304,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
                        NotificationManagerService.CancelNotificationRunnable.class);
        verify(handler, times(1)).scheduleCancelNotification(
                captor.capture());
                captor.capture(), eq(0));
        // Run the runnable given to the cancel notification, and see if it logs properly
        NotificationManagerService.CancelNotificationRunnable runnable = captor.getValue();
@@ -8647,34 +8648,128 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
    }
    @Test
    public void testOnNotificationActionClickLifetimeExtendedEnds() {
    public void testActionClickLifetimeExtendedCancel() throws Exception {
        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
        final Notification.Action action =
                new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
                        mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
        final boolean generatedByAssistant = false;
        // Creates a notification marked as being lifetime extended.
        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
        r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
        mService.addNotification(r);
        StatusBarNotification[] notifs =
                mBinderService.getActiveNotifications(mPkg);
        assertThat(notifs.length).isEqualTo(1);
        assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
        // Call on action click.
        NotificationVisibility notificationVisibility =
                NotificationVisibility.obtain(r.getKey(), 1, 2, true);
        mService.mNotificationDelegate.onNotificationActionClick(
                10, 10, r.getKey(), /*actionIndex=*/2, action, notificationVisibility,
                /*generatedByAssistant=*/false);
        // The flag is removed, so the notification is no longer lifetime extended.
        // Lifetime extended flag persists.
        assertThat(r.getSbn().getNotification().flags
                & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(0);
                & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isGreaterThan(0);
        // The record is sent out without the flag.
        ArgumentCaptor<NotificationRecord> captor =
                ArgumentCaptor.forClass(NotificationRecord.class);
        verify(mAssistants, times(1)).notifyAssistantActionClicked(
                captor.capture(), eq(action), eq(generatedByAssistant));
        assertThat(captor.getValue().getNotification().flags
                & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(0);
        mTestableLooper.moveTimeForward(210);
        waitForIdle();
        verify(mWorkerHandler, times(1))
                .scheduleCancelNotification(
                        any(NotificationManagerService.CancelNotificationRunnable.class), eq(200));
        // Check that the cancelation occurred and the notification is gone.
        notifs = mBinderService.getActiveNotifications(mPkg);
        assertThat(notifs.length).isEqualTo(0);
        assertThat(mService.getNotificationRecordCount()).isEqualTo(0);
    }
    @Test
    public void testActionClickLifetimeExtendedCancel_PreventByNoDismiss() throws Exception {
        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
        final Notification.Action action =
                new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
                        mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
        // Creates a notification marked as being lifetime extended.
        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
        r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
        // Make the notification non-dismissable
        r.getSbn().getNotification().flags |= FLAG_NO_DISMISS;
        mService.addNotification(r);
        StatusBarNotification[] notifs =
                mBinderService.getActiveNotifications(mPkg);
        assertThat(notifs.length).isEqualTo(1);
        assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
        // Call on action click.
        NotificationVisibility notificationVisibility =
                NotificationVisibility.obtain(r.getKey(), 1, 2, true);
        mService.mNotificationDelegate.onNotificationActionClick(
                10, 10, r.getKey(), /*actionIndex=*/2, action, notificationVisibility,
                /*generatedByAssistant=*/false);
        // Lifetime extended flag persists.
        assertThat(r.getSbn().getNotification().flags
                & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isGreaterThan(0);
        mTestableLooper.moveTimeForward(210);
        waitForIdle();
        verify(mWorkerHandler, times(1))
                .scheduleCancelNotification(
                        any(NotificationManagerService.CancelNotificationRunnable.class), eq(200));
        // The cancellation is dropped and the notification is still present, with the update.
        notifs = mBinderService.getActiveNotifications(mPkg);
        assertThat(notifs.length).isEqualTo(1);
        assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
    }
    @Test
    public void testUpdateOnActionClickDropsLifetimeExtendedCancel() throws Exception {
        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
        final Notification.Action action =
                new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
                        mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
        // Creates a notification marked as being lifetime extended.
        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
        r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
        mService.addNotification(r);
        StatusBarNotification[] notifs =
                mBinderService.getActiveNotifications(mPkg);
        assertThat(notifs.length).isEqualTo(1);
        assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
        // Call on action click.
        NotificationVisibility notificationVisibility =
                NotificationVisibility.obtain(r.getKey(), 1, 2, true);
        mService.mNotificationDelegate.onNotificationActionClick(
                10, 10, r.getKey(), /*actionIndex=*/2, action, notificationVisibility,
                /*generatedByAssistant=*/false);
        // The "app" sends an update of the notification in response.
        mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
                r.getSbn().getId(), r.getSbn().getNotification(), r.getSbn().getUserId());
        mTestableLooper.moveTimeForward(210);
        waitForIdle();
        verify(mWorkerHandler, times(1))
                .scheduleCancelNotification(
                        any(NotificationManagerService.CancelNotificationRunnable.class), eq(200));
        // The cancellation is dropped and the notification is still present, with the update.
        notifs = mBinderService.getActiveNotifications(mPkg);
        assertThat(notifs.length).isEqualTo(1);
        assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
    }
    @Test
@@ -8702,6 +8797,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
                mNotificationRecordLogger.event(0));
    }
    @Test
    public void testLogSmartSuggestionsVisible_triggerOnExpandAndVisible() {
        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);