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

Commit d04af510 authored by Evan Laird's avatar Evan Laird
Browse files

Upgrade NoMan's cancelation reposting logic

1. Technically there can be multiple notifications enqueued which
haven't posted yet. Check the entire set of them instead of just the
first.
2. If there is a posted notification that showed up _after_ the request
to cancel, then cancel the cancel (don't cancel is what I'm saying)

Bug: 147420515
Test: atest NotificationManagerServiceTest
Change-Id: I0f401eb3cc6b8c15fb34593f7afc3293daed0726
parent c22d4531
Loading
Loading
Loading
Loading
+48 −4
Original line number Diff line number Diff line
@@ -6128,6 +6128,7 @@ public class NotificationManagerService extends SystemService {
        private final int mRank;
        private final int mCount;
        private final ManagedServiceInfo mListener;
        private final long mWhen;

        CancelNotificationRunnable(final int callingUid, final int callingPid,
                final String pkg, final String tag, final int id,
@@ -6147,6 +6148,7 @@ public class NotificationManagerService extends SystemService {
            this.mRank = rank;
            this.mCount = count;
            this.mListener = listener;
            this.mWhen = System.currentTimeMillis();
        }

        @Override
@@ -6158,13 +6160,28 @@ public class NotificationManagerService extends SystemService {
            }

            synchronized (mNotificationLock) {
                // If the notification is currently enqueued, repost this runnable so it has a
                // chance to notify listeners
                if ((findNotificationByListLocked(mEnqueuedNotifications, mPkg, mTag, mId, mUserId))
                        != null) {
                // Check to see if there is a notification in the enqueued list that hasn't had a
                // chance to post yet.
                List<NotificationRecord> enqueued = findEnqueuedNotificationsForCriteria(
                        mPkg, mTag, mId, mUserId);
                boolean repost = false;
                if (enqueued.size() > 0) {
                    // Found something, let's see what it was
                    repost = true;
                    // If all enqueues happened before this cancel then wait for them to happen,
                    // otherwise we should let this cancel through so the next enqueue happens
                    for (NotificationRecord r : enqueued) {
                        if (r.mUpdateTimeMs > mWhen) {
                            // At least one enqueue was posted after the cancel, so we're invalid
                            return;
                        }
                    }
                }
                if (repost) {
                    mHandler.post(this);
                    return;
                }

                // Look for the notification in the posted list, since we already checked enqueued.
                NotificationRecord r =
                        findNotificationByListLocked(mNotificationList, mPkg, mTag, mId, mUserId);
@@ -6183,6 +6200,10 @@ public class NotificationManagerService extends SystemService {
                    if ((r.getNotification().flags & mMustNotHaveFlags) != 0) {
                        return;
                    }
                    if (r.getUpdateTimeMs() > mWhen) {
                        // In this case, a post must have slipped by when this runnable reposted
                        return;
                    }

                    // Bubbled children get to stick around if the summary was manually cancelled
                    // (user removed) from systemui.
@@ -8104,6 +8125,29 @@ public class NotificationManagerService extends SystemService {
        return null;
    }

    /**
     * There may be multiple records that match your criteria. For instance if there have been
     * multiple notifications posted which are enqueued for the same pkg, tag, id, userId. This
     * method will find all of them in the given list
     * @return
     */
    @GuardedBy("mNotificationLock")
    private List<NotificationRecord> findEnqueuedNotificationsForCriteria(
            String pkg, String tag, int id, int userId) {
        final ArrayList<NotificationRecord> records = new ArrayList<>();
        final int n = mEnqueuedNotifications.size();
        for (int i = 0; i < n; i++) {
            NotificationRecord r = mEnqueuedNotifications.get(i);
            if (notificationMatchesUserId(r, userId)
                    && r.getSbn().getId() == id
                    && TextUtils.equals(r.getSbn().getTag(), tag)
                    && r.getSbn().getPackageName().equals(pkg)) {
                records.add(r);
            }
        }
        return records;
    }

    @GuardedBy("mNotificationLock")
    int indexOfNotificationLocked(String key) {
        final int N = mNotificationList.size();
+4 −0
Original line number Diff line number Diff line
@@ -906,6 +906,10 @@ public final class NotificationRecord {
        return (int) (now - mInterruptionTimeMs);
    }

    public long getUpdateTimeMs() {
        return mUpdateTimeMs;
    }

    /**
     * Set the visibility of the notification.
     */
+21 −0
Original line number Diff line number Diff line
@@ -1263,6 +1263,27 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        assertEquals(0, mService.getNotificationRecordCount());
    }

    @Test
    public void testPostCancelPostNotifiesListeners() throws Exception {
        // WHEN a notification is posted
        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
                sbn.getNotification(), sbn.getUserId());
        // THEN it is canceled
        mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getUserId());
        // THEN it is posted again (before the cancel has a chance to finish)
        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
                sbn.getNotification(), sbn.getUserId());
        // THEN the later enqueue isn't swallowed by the cancel. I.e., ordering is respected
        waitForIdle();

        // The final enqueue made it to the listener instead of being canceled
        StatusBarNotification[] notifs =
                mBinderService.getActiveNotifications(PKG);
        assertEquals(1, notifs.length);
        assertEquals(1, mService.getNotificationRecordCount());
    }

    @Test
    public void testCancelNotificationWhilePostedAndEnqueued() throws Exception {
        mBinderService.enqueueNotificationWithTag(PKG, PKG,