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

Commit ef3f660d authored by Yuri Lin's avatar Yuri Lin
Browse files

Log assistant notification cancels accordingly.

This change adds the NOTIFICATION_CANCEL_ASSISTANT uievent and reports any cancellations due to assistants as REASON_ASSISTANT_CANCEL. Listeners below T (when this reason is added) are notified with the same reason that was previously used in all cases, REASON_LISTENER_CANCEL.

Bug: 201417230
Test: NotificationManagerServiceTest, manually by statsd_testdrive

Change-Id: Iae50053d325b62b479b3c31aea97d35d8847f381
parent 37da147f
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -38870,6 +38870,7 @@ package android.service.notification {
    field public static final int NOTIFICATION_CHANNEL_OR_GROUP_UPDATED = 2; // 0x2
    field public static final int REASON_APP_CANCEL = 8; // 0x8
    field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9
    field public static final int REASON_ASSISTANT_CANCEL = 22; // 0x16
    field public static final int REASON_CANCEL = 2; // 0x2
    field public static final int REASON_CANCEL_ALL = 3; // 0x3
    field public static final int REASON_CHANNEL_BANNED = 17; // 0x11
+6 −1
Original line number Diff line number Diff line
@@ -256,6 +256,8 @@ public abstract class NotificationListenerService extends Service {
    public static final int REASON_CHANNEL_REMOVED = 20;
    /** Notification was canceled due to the app's storage being cleared */
    public static final int REASON_CLEAR_DATA = 21;
    /** Notification was canceled due to an assistant adjustment update. */
    public static final int REASON_ASSISTANT_CANCEL = 22;

    /**
     * @hide
@@ -279,7 +281,10 @@ public abstract class NotificationListenerService extends Service {
            REASON_UNAUTOBUNDLED,
            REASON_CHANNEL_BANNED,
            REASON_SNOOZED,
            REASON_TIMEOUT
            REASON_TIMEOUT,
            REASON_CHANNEL_REMOVED,
            REASON_CLEAR_DATA,
            REASON_ASSISTANT_CANCEL,
    })
    public @interface NotificationCancelReason{};

+31 −4
Original line number Diff line number Diff line
@@ -83,6 +83,7 @@ import static android.service.notification.NotificationListenerService.NOTIFICAT
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
import static android.service.notification.NotificationListenerService.REASON_ASSISTANT_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
@@ -475,6 +476,14 @@ public class NotificationManagerService extends SystemService {
    @LoggingOnly
    private static final long RATE_LIMIT_TOASTS = 174840628L;
    /**
     * Whether listeners understand the more specific reason provided for notification
     * cancellations from an assistant, rather than using the more general REASON_LISTENER_CANCEL.
     */
    @ChangeId
    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2)
    private static final long NOTIFICATION_LOG_ASSISTANT_CANCEL = 195579280L;
    private IActivityManager mAm;
    private ActivityTaskManagerInternal mAtm;
    private ActivityManager mActivityManager;
@@ -4387,6 +4396,13 @@ public class NotificationManagerService extends SystemService {
                synchronized (mNotificationLock) {
                    final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
                    // Cancellation reason. If the token comes from assistant, label the
                    // cancellation as coming from the assistant; default to LISTENER_CANCEL.
                    int reason = REASON_LISTENER_CANCEL;
                    if (mAssistants.isServiceTokenValidLocked(token)) {
                        reason = REASON_ASSISTANT_CANCEL;
                    }
                    if (keys != null) {
                        final int N = keys.length;
                        for (int i = 0; i < N; i++) {
@@ -4399,7 +4415,7 @@ public class NotificationManagerService extends SystemService {
                            }
                            cancelNotificationFromListenerLocked(info, callingUid, callingPid,
                                    r.getSbn().getPackageName(), r.getSbn().getTag(),
                                    r.getSbn().getId(), userId);
                                    r.getSbn().getId(), userId, reason);
                        }
                    } else {
                        cancelAllLocked(callingUid, callingPid, info.userid,
@@ -4493,12 +4509,13 @@ public class NotificationManagerService extends SystemService {
         */
        @GuardedBy("mNotificationLock")
        private void cancelNotificationFromListenerLocked(ManagedServiceInfo info,
                int callingUid, int callingPid, String pkg, String tag, int id, int userId) {
                int callingUid, int callingPid, String pkg, String tag, int id, int userId,
                int reason) {
            int mustNotHaveFlags = FLAG_ONGOING_EVENT;
            cancelNotification(callingUid, callingPid, pkg, tag, id, 0 /* mustHaveFlags */,
                    mustNotHaveFlags,
                    true,
                    userId, REASON_LISTENER_CANCEL, info);
                    userId, reason, info);
        }
        /**
@@ -4640,13 +4657,17 @@ public class NotificationManagerService extends SystemService {
            try {
                synchronized (mNotificationLock) {
                    final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
                    int cancelReason = REASON_LISTENER_CANCEL;
                    if (mAssistants.isServiceTokenValidLocked(token)) {
                        cancelReason = REASON_ASSISTANT_CANCEL;
                    }
                    if (info.supportsProfiles()) {
                        Slog.e(TAG, "Ignoring deprecated cancelNotification(pkg, tag, id) "
                                + "from " + info.component
                                + " use cancelNotification(key) instead.");
                    } else {
                        cancelNotificationFromListenerLocked(info, callingUid, callingPid,
                                pkg, tag, id, info.userid);
                                pkg, tag, id, info.userid, cancelReason);
                    }
                }
            } finally {
@@ -11022,6 +11043,12 @@ public class NotificationManagerService extends SystemService {
                        && (reason == REASON_CHANNEL_REMOVED || reason == REASON_CLEAR_DATA)) {
                    reason = REASON_CHANNEL_BANNED;
                }
                // apps before T don't know about REASON_ASSISTANT, so replace it with the
                // previously-used case, REASON_LISTENER_CANCEL
                if (!CompatChanges.isChangeEnabled(NOTIFICATION_LOG_ASSISTANT_CANCEL, info.uid)
                        && reason == REASON_ASSISTANT_CANCEL) {
                    reason = REASON_LISTENER_CANCEL;
                }
                listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
            } catch (RemoteException ex) {
                Slog.e(TAG, "unable to notify listener (removed): " + info, ex);
+7 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.notification;

import static android.service.notification.NotificationListenerService.REASON_ASSISTANT_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CLICK;
import static android.service.notification.NotificationListenerService.REASON_TIMEOUT;
@@ -180,7 +181,9 @@ public interface NotificationRecordLogger {
                + " shade.")
        NOTIFICATION_CANCEL_USER_SHADE(192),
        @UiEvent(doc = "Notification was canceled due to user dismissal from the lockscreen")
        NOTIFICATION_CANCEL_USER_LOCKSCREEN(193);
        NOTIFICATION_CANCEL_USER_LOCKSCREEN(193),
        @UiEvent(doc = "Notification was canceled due to an assistant adjustment update.")
        NOTIFICATION_CANCEL_ASSISTANT(906);

        private final int mId;
        NotificationCancelledEvent(int id) {
@@ -206,6 +209,9 @@ public interface NotificationRecordLogger {
                if ((REASON_CLICK <= reason) && (reason <= REASON_TIMEOUT)) {
                    return NotificationCancelledEvent.values()[reason];
                }
                if (reason == REASON_ASSISTANT_CANCEL) {
                    return NotificationCancelledEvent.NOTIFICATION_CANCEL_ASSISTANT;
                }
                if (NotificationManagerService.DBG) {
                    throw new IllegalArgumentException("Unexpected cancel reason " + reason);
                }
+53 −0
Original line number Diff line number Diff line
@@ -4653,6 +4653,59 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        assertEquals(2, mNotificationRecordLogger.get(1).getInstanceId());
    }

    @Test
    public void testAdjustmentToImportanceNone_cancelsNotification() throws Exception {
        NotificationManagerService.WorkerHandler handler = mock(
                NotificationManagerService.WorkerHandler.class);
        mService.setHandler(handler);
        when(mAssistants.isSameUser(eq(null), anyInt())).thenReturn(true);
        when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);

        // Set up notifications: r1 is adjusted, r2 is not
        final NotificationRecord r1 = generateNotificationRecord(
                mTestNotificationChannel, 1, null, true);
        r1.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
        mService.addNotification(r1);
        final NotificationRecord r2 = generateNotificationRecord(
                mTestNotificationChannel, 2, null, true);
        r2.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
        mService.addNotification(r2);

        // Test an adjustment that sets importance to none (meaning it's cancelling)
        Bundle signals1 = new Bundle();
        signals1.putInt(Adjustment.KEY_IMPORTANCE, IMPORTANCE_NONE);
        Adjustment adjustment1 = new Adjustment(
                r1.getSbn().getPackageName(), r1.getKey(), signals1, "",
                r1.getUser().getIdentifier());

        mBinderService.applyAdjustmentFromAssistant(null, adjustment1);

        // Actually apply the adjustments & recalculate importance when run
        doAnswer(invocationOnMock -> {
            ((NotificationRecord) invocationOnMock.getArguments()[0])
                    .applyAdjustments();
            ((NotificationRecord) invocationOnMock.getArguments()[0])
                    .calculateImportance();
            return null;
        }).when(mRankingHelper).extractSignals(any(NotificationRecord.class));

        // run the CancelNotificationRunnable when it happens
        ArgumentCaptor<NotificationManagerService.CancelNotificationRunnable> captor =
                ArgumentCaptor.forClass(
                        NotificationManagerService.CancelNotificationRunnable.class);

        verify(handler, times(1)).scheduleCancelNotification(
                captor.capture());

        // Run the runnable given to the cancel notification, and see if it logs properly
        NotificationManagerService.CancelNotificationRunnable runnable = captor.getValue();
        runnable.run();
        assertEquals(1, mNotificationRecordLogger.numCalls());
        assertEquals(
                NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_ASSISTANT,
                mNotificationRecordLogger.event(0));
    }

    @Test
    public void testEnqueuedAdjustmentAppliesAdjustments() throws Exception {
        final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);