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

Commit 7e0d649a authored by Beverly's avatar Beverly
Browse files

Add NotifCollection dismiss methods

Now NotifCollection has three methods for sending dismissals to system
server:
1. dismissNotification
	- dismisses a single notification
	- use case: when user swipes away a single notification
	- ends up calling IStatusBarService#onNotificationClear to
	update system server
2. dismissNotifications - dismisses multiple notifications it's passed.
	- use case: user dismisses all 'gentle/silent' notifications
	- ends up calling dismissNotification (#1) multiple times
3. dismissAllNotification
	- dismisses all clearable notifications associated with a given userId
	- use case: user clicks on "clear all" to clear all their notifications
	- ends up calling IStatusBarService#onClearAllNotifications to
	update system server

Test: atest NotifCollectionTest
Test: atest SystemUITests
Change-Id: I1fdf38fd192824b1dfc45dd3a5e1fea0a51e2e75
Fixes: 149241859
parent 63d4441d
Loading
Loading
Loading
Loading
+138 −43
Original line number Diff line number Diff line
@@ -44,12 +44,16 @@ import static java.util.Objects.requireNonNull;
import android.annotation.IntDef;
import android.annotation.MainThread;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.Notification;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.Pair;

import androidx.annotation.NonNull;

@@ -191,38 +195,36 @@ public class NotifCollection implements Dumpable {
    }

    /**
     * Dismiss a notification on behalf of the user.
     * Dismisses multiple notifications on behalf of the user.
     */
    public void dismissNotification(NotificationEntry entry, @NonNull DismissedByUserStats stats) {
    public void dismissNotifications(
            List<Pair<NotificationEntry, DismissedByUserStats>> entriesToDismiss) {
        Assert.isMainThread();
        requireNonNull(stats);
        checkForReentrantCall();

        final List<NotificationEntry> entriesToLocallyDismiss = new ArrayList<>();
        for (int i = 0; i < entriesToDismiss.size(); i++) {
            NotificationEntry entry = entriesToDismiss.get(i).first;
            DismissedByUserStats stats = entriesToDismiss.get(i).second;

            requireNonNull(stats);
            if (entry != mNotificationSet.get(entry.getKey())) {
                throw new IllegalStateException("Invalid entry: " + entry.getKey());
            }

            if (entry.getDismissState() == DISMISSED) {
            return;
                continue;
            }

            updateDismissInterceptors(entry);
            if (isDismissIntercepted(entry)) {
                mLogger.logNotifDismissedIntercepted(entry.getKey());
            return;
                continue;
            }

        // Optimistically mark the notification as dismissed -- we'll wait for the signal from
        // system server before removing it from our notification set.
        entry.setDismissState(DISMISSED);
        mLogger.logNotifDismissed(entry.getKey());

        List<NotificationEntry> canceledEntries = new ArrayList<>();

        if (isCanceled(entry)) {
            canceledEntries.add(entry);
        } else {
            // Ask system server to remove it for us
            entriesToLocallyDismiss.add(entry);
            if (!isCanceled(entry)) {
                // send message to system server if this notification hasn't already been cancelled
                try {
                    mStatusBarService.onNotificationClear(
                            entry.getSbn().getPackageName(),
@@ -236,11 +238,74 @@ public class NotifCollection implements Dumpable {
                } catch (RemoteException e) {
                    // system process is dead if we're here.
                }
            }
        }

        locallyDismissNotifications(entriesToLocallyDismiss);
        rebuildList();
    }

    /**
     * Dismisses a single notification on behalf of the user.
     */
    public void dismissNotification(
            NotificationEntry entry,
            @NonNull DismissedByUserStats stats) {
        dismissNotifications(List.of(
                new Pair<NotificationEntry, DismissedByUserStats>(entry, stats)));
    }

    /**
     * Dismisses all clearable notifications for a given userid on behalf of the user.
     */
    public void dismissAllNotifications(@UserIdInt int userId) {
        Assert.isMainThread();
        checkForReentrantCall();

        try {
            mStatusBarService.onClearAllNotifications(userId);
        } catch (RemoteException e) {
            // system process is dead if we're here.
        }

            // Also mark any children as dismissed as system server will auto-dismiss them as well
        final List<NotificationEntry> entries = new ArrayList(getActiveNotifs());
        for (int i = entries.size() - 1; i >= 0; i--) {
            NotificationEntry entry = entries.get(i);
            if (!shouldDismissOnClearAll(entry, userId)) {
                // system server won't be removing these notifications, but we still give dismiss
                // interceptors the chance to filter the notification
                updateDismissInterceptors(entry);
                if (isDismissIntercepted(entry)) {
                    mLogger.logNotifClearAllDismissalIntercepted(entry.getKey());
                }
                entries.remove(i);
            }
        }

        locallyDismissNotifications(entries);
        rebuildList();
    }

    /**
     * Optimistically marks the given notifications as dismissed -- we'll wait for the signal
     * from system server before removing it from our notification set.
     */
    private void locallyDismissNotifications(List<NotificationEntry> entries) {
        final List<NotificationEntry> canceledEntries = new ArrayList<>();

        for (int i = 0; i < entries.size(); i++) {
            NotificationEntry entry = entries.get(i);

            entry.setDismissState(DISMISSED);
            mLogger.logNotifDismissed(entry.getKey());

            if (isCanceled(entry)) {
                canceledEntries.add(entry);
            } else {
                // Mark any children as dismissed as system server will auto-dismiss them as well
                if (entry.getSbn().getNotification().isGroupSummary()) {
                    for (NotificationEntry otherEntry : mNotificationSet.values()) {
                    if (shouldAutoDismiss(otherEntry, entry.getSbn().getGroupKey())) {
                        if (shouldAutoDismissChildren(otherEntry, entry.getSbn().getGroupKey())) {
                            otherEntry.setDismissState(PARENT_DISMISSED);
                            if (isCanceled(otherEntry)) {
                                canceledEntries.add(otherEntry);
@@ -249,13 +314,13 @@ public class NotifCollection implements Dumpable {
                    }
                }
            }
        }

        // Immediately remove any dismissed notifs that have already been canceled by system server
        // (probably due to being lifetime-extended up until this point).
        for (NotificationEntry canceledEntry : canceledEntries) {
            tryRemoveNotification(canceledEntry);
        }
        rebuildList();
    }

    private void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
@@ -552,7 +617,7 @@ public class NotifCollection implements Dumpable {
     *
     * See NotificationManager.cancelGroupChildrenByListLocked() for corresponding code.
     */
    private static boolean shouldAutoDismiss(
    private static boolean shouldAutoDismissChildren(
            NotificationEntry entry,
            String dismissedGroupKey) {
        return entry.getSbn().getGroupKey().equals(dismissedGroupKey)
@@ -562,10 +627,39 @@ public class NotifCollection implements Dumpable {
                && entry.getDismissState() != DISMISSED;
    }

    /**
     * When the user 'clears all notifications' through SystemUI, NotificationManager will not
     * dismiss unclearable notifications.
     * @return true if we think NotificationManager will dismiss the entry when asked to
     * cancel this notification with {@link NotificationListenerService#REASON_CANCEL_ALL}
     *
     * See NotificationManager.cancelAllLocked for corresponding code.
     */
    private static boolean shouldDismissOnClearAll(
            NotificationEntry entry,
            @UserIdInt int userId) {
        // TODO: (b/149396544) add FLAG_BUBBLE check here + in NoManService
        return userIdMatches(entry, userId)
                && entry.isClearable()
                && entry.getDismissState() != DISMISSED;
    }

    private static boolean hasFlag(NotificationEntry entry, int flag) {
        return (entry.getSbn().getNotification().flags & flag) != 0;
    }

    /**
     * Determine whether the userId applies to the notification in question, either because
     * they match exactly, or one of them is USER_ALL (which is treated as a wildcard).
     *
     * See NotificationManager#notificationMatchesUserId
     */
    private static boolean userIdMatches(NotificationEntry entry, int userId) {
        return userId == UserHandle.USER_ALL
                || entry.getSbn().getUser().getIdentifier() == UserHandle.USER_ALL
                || entry.getSbn().getUser().getIdentifier() == userId;
    }

    private void dispatchOnEntryInit(NotificationEntry entry) {
        mAmDispatchingToOtherCode = true;
        for (NotifCollectionListener listener : mNotifCollectionListeners) {
@@ -613,6 +707,7 @@ public class NotifCollection implements Dumpable {
        }
        mAmDispatchingToOtherCode = false;
    }

    @Override
    public void dump(@NonNull FileDescriptor fd, PrintWriter pw, @NonNull String[] args) {
        final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs());
+5 −2
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ public class NotifInflaterImpl implements NotifInflater {
    private final IStatusBarService mStatusBarService;
    private final NotifCollection mNotifCollection;
    private final NotifInflationErrorManager mNotifErrorManager;
    private final NotifPipeline mNotifPipeline;

    private NotificationRowBinderImpl mNotificationRowBinder;
    private InflationCallback mExternalInflationCallback;
@@ -52,10 +53,12 @@ public class NotifInflaterImpl implements NotifInflater {
    public NotifInflaterImpl(
            IStatusBarService statusBarService,
            NotifCollection notifCollection,
            NotifInflationErrorManager errorManager) {
            NotifInflationErrorManager errorManager,
            NotifPipeline notifPipeline) {
        mStatusBarService = statusBarService;
        mNotifCollection = notifCollection;
        mNotifErrorManager = errorManager;
        mNotifPipeline = notifPipeline;
    }

    /**
@@ -110,7 +113,7 @@ public class NotifInflaterImpl implements NotifInflater {
                                DISMISS_SENTIMENT_NEUTRAL,
                                NotificationVisibility.obtain(entry.getKey(),
                                        entry.getRanking().getRank(),
                                        mNotifCollection.getActiveNotifs().size(),
                                        mNotifPipeline.getShadeListCount(),
                                        true,
                                        NotificationLogger.getNotificationLocation(entry))
                        ));
+23 −0
Original line number Diff line number Diff line
@@ -195,4 +195,27 @@ public class NotifPipeline implements CommonNotifCollection {
    public List<ListEntry> getShadeList() {
        return mShadeListBuilder.getShadeList();
    }

    /**
     * Returns the number of notifications currently shown in the shade. This includes all
     * children and summary notifications. If this method is called during pipeline execution it
     * will return the number of notifications in its current state, which will likely be only
     * partially-generated.
     */
    public int getShadeListCount() {
        final List<ListEntry> entries = getShadeList();
        int numNotifs = 0;
        for (int i = 0; i < entries.size(); i++) {
            final ListEntry entry = entries.get(i);
            if (entry instanceof GroupEntry) {
                final GroupEntry parentEntry = (GroupEntry) entry;
                numNotifs++; // include the summary in the count
                numNotifs += parentEntry.getChildren().size();
            } else {
                numNotifs++;
            }
        }

        return numNotifs;
    }
}
+3 −3
Original line number Diff line number Diff line
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.notification.collection.coordinator;

import static android.service.notification.NotificationStats.DISMISSAL_OTHER;
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_UNKNOWN;
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;

import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.bubbles.BubbleController;
@@ -153,10 +153,10 @@ public class BubbleCoordinator implements Coordinator {
    private DismissedByUserStats createDismissedByUserStats(NotificationEntry entry) {
        return new DismissedByUserStats(
                DISMISSAL_OTHER,
                DISMISS_SENTIMENT_UNKNOWN,
                DISMISS_SENTIMENT_NEUTRAL,
                NotificationVisibility.obtain(entry.getKey(),
                        entry.getRanking().getRank(),
                        mNotifPipeline.getActiveNotifs().size(),
                        mNotifPipeline.getShadeListCount(),
                        true, // was visible as a bubble
                        NotificationLogger.getNotificationLocation(entry))
        );
+8 −0
Original line number Diff line number Diff line
@@ -77,6 +77,14 @@ class NotifCollectionLogger @Inject constructor(
        })
    }

    fun logNotifClearAllDismissalIntercepted(key: String) {
        buffer.log(TAG, INFO, {
            str1 = key
        }, {
            "CLEAR ALL DISMISSAL INTERCEPTED $str1"
        })
    }

    fun logRankingMissing(key: String, rankingMap: RankingMap) {
        buffer.log(TAG, WARNING, { str1 = key }, { "Ranking update is missing ranking for $str1" })
        buffer.log(TAG, DEBUG, {}, { "Ranking map contents:" })
Loading