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

Commit e64930b2 authored by Julia Reynolds's avatar Julia Reynolds Committed by Automerger Merge Worker
Browse files

Merge "Let auto summaries inherit more flags from children" into udc-dev am: c02f57c4

parents 369d294b c02f57c4
Loading
Loading
Loading
Loading
+127 −114
Original line number Diff line number Diff line
@@ -15,40 +15,49 @@
 */
package com.android.server.notification;

import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
import static android.app.Notification.FLAG_AUTO_CANCEL;
import static android.app.Notification.FLAG_GROUP_SUMMARY;
import static android.app.Notification.FLAG_LOCAL_ONLY;
import static android.app.Notification.FLAG_NO_CLEAR;
import static android.app.Notification.FLAG_ONGOING_EVENT;

import android.annotation.NonNull;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;

/**
 * NotificationManagerService helper for auto-grouping notifications.
 */
public class GroupHelper {
    private static final String TAG = "GroupHelper";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    protected static final String AUTOGROUP_KEY = "ranker_group";

    // Flags that all autogroup summaries have
    protected static final int BASE_FLAGS =
            FLAG_AUTOGROUP_SUMMARY | FLAG_GROUP_SUMMARY | FLAG_LOCAL_ONLY;
    // Flag that autogroup summaries inherits if all children have the flag
    private static final int ALL_CHILDREN_FLAG = FLAG_AUTO_CANCEL;
    // Flags that autogroup summaries inherits if any child has them
    private static final int ANY_CHILDREN_FLAGS = FLAG_ONGOING_EVENT | FLAG_NO_CLEAR;

    private final Callback mCallback;
    private final int mAutoGroupAtCount;

    // count the number of ongoing notifications per group
    // userId|packageName -> (set of ongoing notifications that aren't in an app group)
    final ArrayMap<String, ArraySet<String>>
            mOngoingGroupCount = new ArrayMap<>();

    // Map of user : <Map of package : notification keys>. Only contains notifications that are not
    // grouped by the app (aka no group or sort key).
    Map<Integer, Map<String, LinkedHashSet<String>>> mUngroupedNotifications = new HashMap<>();
    // Only contains notifications that are not explicitly grouped by the app (aka no group or
    // sort key).
    // userId|packageName -> (keys of notifications that aren't in an explicit app group -> flags)
    @GuardedBy("mUngroupedNotifications")
    private final ArrayMap<String, ArrayMap<String, Integer>> mUngroupedNotifications
            = new ArrayMap<>();

    public GroupHelper(int autoGroupAtCount, Callback callback) {
        mAutoGroupAtCount = autoGroupAtCount;
@@ -60,69 +69,30 @@ public class GroupHelper {
    }

    @VisibleForTesting
    protected int getOngoingGroupCount(int userId, String pkg) {
        String key = generatePackageKey(userId, pkg);
        return mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0)).size();
    @GuardedBy("mUngroupedNotifications")
    protected int getAutogroupSummaryFlags(@NonNull final ArrayMap<String, Integer> children) {
        boolean allChildrenHasFlag = children.size() > 0;
        int anyChildFlagSet = 0;
        for (int i = 0; i < children.size(); i++) {
            if (!hasAnyFlag(children.valueAt(i), ALL_CHILDREN_FLAG)) {
                allChildrenHasFlag = false;
            }

    private void updateOngoingGroupCount(StatusBarNotification sbn, boolean add) {
        if (sbn.getNotification().isGroupSummary()) {
            return;
            if (hasAnyFlag(children.valueAt(i), ANY_CHILDREN_FLAGS)) {
                anyChildFlagSet |= (children.valueAt(i) & ANY_CHILDREN_FLAGS);
            }
        String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
        ArraySet<String> notifications = mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0));
        if (add) {
            notifications.add(sbn.getKey());
            mOngoingGroupCount.put(key, notifications);
        } else {
            notifications.remove(sbn.getKey());
            // we don't need to put it back if it is default
        }

        boolean needsOngoingFlag = notifications.size() > 0;
        mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), needsOngoingFlag);
        return BASE_FLAGS | (allChildrenHasFlag ? ALL_CHILDREN_FLAG : 0) | anyChildFlagSet;
    }

    public void onNotificationUpdated(StatusBarNotification childSbn) {
        updateOngoingGroupCount(childSbn, childSbn.isOngoing() && !childSbn.isAppGroup());
    private boolean hasAnyFlag(int flags, int mask) {
        return (flags & mask) != 0;
    }

    public void onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists) {
        try {
            updateOngoingGroupCount(sbn, sbn.isOngoing() && !sbn.isAppGroup());

            List<String> notificationsToGroup = new ArrayList<>();
            if (!sbn.isAppGroup()) {
                // Not grouped by the app, add to the list of notifications for the app;
                // send grouping update if app exceeds the autogrouping limit.
                synchronized (mUngroupedNotifications) {
                    Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser
                            = mUngroupedNotifications.get(sbn.getUserId());
                    if (ungroupedNotificationsByUser == null) {
                        ungroupedNotificationsByUser = new HashMap<>();
                    }
                    mUngroupedNotifications.put(sbn.getUserId(), ungroupedNotificationsByUser);
                    LinkedHashSet<String> notificationsForPackage
                            = ungroupedNotificationsByUser.get(sbn.getPackageName());
                    if (notificationsForPackage == null) {
                        notificationsForPackage = new LinkedHashSet<>();
                    }

                    notificationsForPackage.add(sbn.getKey());
                    ungroupedNotificationsByUser.put(sbn.getPackageName(), notificationsForPackage);

                    if (notificationsForPackage.size() >= mAutoGroupAtCount
                            || autogroupSummaryExists) {
                        notificationsToGroup.addAll(notificationsForPackage);
                    }
                }
                if (notificationsToGroup.size() > 0) {
                    adjustAutogroupingSummary(sbn.getUserId(), sbn.getPackageName(),
                            notificationsToGroup.get(0), true);
                    adjustNotificationBundling(notificationsToGroup, true);
                }
                maybeGroup(sbn, autogroupSummaryExists);
            } else {
                // Grouped, but not by us. Send updates to un-autogroup, if we grouped it.
                maybeUngroup(sbn, false, sbn.getUserId());
            }

@@ -133,7 +103,6 @@ public class GroupHelper {

    public void onNotificationRemoved(StatusBarNotification sbn) {
        try {
            updateOngoingGroupCount(sbn, false);
            maybeUngroup(sbn, true, sbn.getUserId());
        } catch (Exception e) {
            Slog.e(TAG, "Error processing canceled notification", e);
@@ -141,70 +110,114 @@ public class GroupHelper {
    }

    /**
     * Un-autogroups notifications that are now grouped by the app.
     * A non-app grouped notification has been added or updated
     * Evaluate if:
     * (a) an existing autogroup summary needs updated flags
     * (b) a new autogroup summary needs to be added with correct flags
     * (c) other non-app grouped children need to be moved to the autogroup
     *
     * And stores the list of upgrouped notifications & their flags
     */
    private void maybeGroup(StatusBarNotification sbn, boolean autogroupSummaryExists) {
        int flags = 0;
        List<String> notificationsToGroup = new ArrayList<>();
        synchronized (mUngroupedNotifications) {
            String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
            final ArrayMap<String, Integer> children =
                    mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());

            children.put(sbn.getKey(), sbn.getNotification().flags);
            mUngroupedNotifications.put(key, children);

            if (children.size() >= mAutoGroupAtCount || autogroupSummaryExists) {
                flags = getAutogroupSummaryFlags(children);
                notificationsToGroup.addAll(children.keySet());
            }
        }
        if (notificationsToGroup.size() > 0) {
            if (autogroupSummaryExists) {
                mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), flags);
            } else {
                mCallback.addAutoGroupSummary(
                        sbn.getUserId(), sbn.getPackageName(), sbn.getKey(), flags);
            }
            for (String key : notificationsToGroup) {
                mCallback.addAutoGroup(key);
            }
        }
    }

    /**
     * A notification was added that's app grouped, or a notification was removed.
     * Evaluate whether:
     * (a) an existing autogroup summary needs updated flags
     * (b) if we need to remove our autogroup overlay for this notification
     * (c) we need to remove the autogroup summary
     *
     * And updates the internal state of un-app-grouped notifications and their flags
     */
    private void maybeUngroup(StatusBarNotification sbn, boolean notificationGone, int userId) {
        List<String> notificationsToUnAutogroup = new ArrayList<>();
        boolean removeSummary = false;
        int summaryFlags = 0;
        boolean updateSummaryFlags = false;
        boolean removeAutogroupOverlay = false;
        synchronized (mUngroupedNotifications) {
            Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser
                    = mUngroupedNotifications.get(sbn.getUserId());
            if (ungroupedNotificationsByUser == null || ungroupedNotificationsByUser.size() == 0) {
                return;
            }
            LinkedHashSet<String> notificationsForPackage
                    = ungroupedNotificationsByUser.get(sbn.getPackageName());
            if (notificationsForPackage == null || notificationsForPackage.size() == 0) {
            String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
            final ArrayMap<String, Integer> children =
                    mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
            if (children.size() == 0) {
                return;
            }
            if (notificationsForPackage.remove(sbn.getKey())) {
                if (!notificationGone) {
                    // Add the current notification to the ungrouping list if it still exists.
                    notificationsToUnAutogroup.add(sbn.getKey());

            // if this notif was autogrouped and now isn't
            if (children.containsKey(sbn.getKey())) {
                // if this notification was contributing flags that aren't covered by other
                // children to the summary, reevaluate flags for the summary
                int flags = children.remove(sbn.getKey());
                // this
                if (hasAnyFlag(flags, ANY_CHILDREN_FLAGS)) {
                    updateSummaryFlags = true;
                    summaryFlags = getAutogroupSummaryFlags(children);
                }
                // if this notification still exists and has an autogroup overlay, but is now
                // grouped by the app, clear the overlay
                if (!notificationGone && sbn.getOverrideGroupKey() != null) {
                    removeAutogroupOverlay = true;
                }
            // If the status change of this notification has brought the number of loose
            // notifications to zero, remove the summary and un-autogroup.
            if (notificationsForPackage.size() == 0) {
                ungroupedNotificationsByUser.remove(sbn.getPackageName());

                // If there are no more children left to autogroup, remove the summary
                if (children.size() == 0) {
                    removeSummary = true;
                }
            }
        if (removeSummary) {
            adjustAutogroupingSummary(userId, sbn.getPackageName(), null, false);
        }
        if (notificationsToUnAutogroup.size() > 0) {
            adjustNotificationBundling(notificationsToUnAutogroup, false);
        if (removeSummary) {
            mCallback.removeAutoGroupSummary(userId, sbn.getPackageName());
        } else {
            if (updateSummaryFlags) {
                mCallback.updateAutogroupSummary(userId, sbn.getPackageName(), summaryFlags);
            }
        }

    private void adjustAutogroupingSummary(int userId, String packageName, String triggeringKey,
            boolean summaryNeeded) {
        if (summaryNeeded) {
            mCallback.addAutoGroupSummary(userId, packageName, triggeringKey,
                    getOngoingGroupCount(userId, packageName) > 0);
        } else {
            mCallback.removeAutoGroupSummary(userId, packageName);
        if (removeAutogroupOverlay) {
            mCallback.removeAutoGroup(sbn.getKey());
        }
    }

    private void adjustNotificationBundling(List<String> keys, boolean group) {
        for (String key : keys) {
            if (DEBUG) Log.i(TAG, "Sending grouping adjustment for: " + key + " group? " + group);
            if (group) {
                mCallback.addAutoGroup(key);
            } else {
                mCallback.removeAutoGroup(key);
            }
    @VisibleForTesting
    int getNotGroupedByAppCount(int userId, String pkg) {
        synchronized (mUngroupedNotifications) {
            String key = generatePackageKey(userId, pkg);
            final ArrayMap<String, Integer> children =
                    mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
            return children.size();
        }
    }

    protected interface Callback {
        void addAutoGroup(String key);
        void removeAutoGroup(String key);
        void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
                boolean needsOngoingFlag);
        void addAutoGroupSummary(int userId, String pkg, String triggeringKey, int flags);
        void removeAutoGroupSummary(int user, String pkg);
        void updateAutogroupSummary(int userId, String pkg, boolean needsOngoingFlag);
        void updateAutogroupSummary(int userId, String pkg, int flags);
    }
}
+29 −45
Original line number Diff line number Diff line
@@ -319,6 +319,7 @@ import com.android.server.pm.PackageManagerService;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.powerstats.StatsPullAtomCallbackImpl;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.utils.Slogf;
@@ -902,11 +903,11 @@ public class NotificationManagerService extends SystemService {
     * has the same flag. It will delete the flag otherwise
     * @param userId user id of the autogroup summary
     * @param pkg package of the autogroup summary
     * @param needsOngoingFlag true if the group has at least one ongoing notification
     * @param flags the new flags for this summary
     * @param isAppForeground true if the app is currently in the foreground.
     */
    @GuardedBy("mNotificationLock")
    protected void updateAutobundledSummaryFlags(int userId, String pkg, boolean needsOngoingFlag,
    protected void updateAutobundledSummaryFlags(int userId, String pkg, int flags,
            boolean isAppForeground) {
        ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
        if (summaries == null) {
@@ -921,13 +922,8 @@ public class NotificationManagerService extends SystemService {
            return;
        }
        int oldFlags = summary.getSbn().getNotification().flags;
        if (needsOngoingFlag) {
            summary.getSbn().getNotification().flags |= FLAG_ONGOING_EVENT;
        } else {
            summary.getSbn().getNotification().flags &= ~FLAG_ONGOING_EVENT;
        }
        if (summary.getSbn().getNotification().flags != oldFlags) {
        if (oldFlags != flags) {
            summary.getSbn().getNotification().flags = flags;
            mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground,
                    SystemClock.elapsedRealtime()));
        }
@@ -2684,9 +2680,14 @@ public class NotificationManagerService extends SystemService {
            @Override
            public void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
                    boolean needsOngoingFlag) {
                NotificationManagerService.this.addAutoGroupSummary(
                        userId, pkg, triggeringKey, needsOngoingFlag);
                    int flags) {
                NotificationRecord r = createAutoGroupSummary(userId, pkg, triggeringKey, flags);
                if (r != null) {
                    final boolean isAppForeground =
                            mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
                    mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground,
                            SystemClock.elapsedRealtime()));
                }
            }
            @Override
@@ -2697,11 +2698,11 @@ public class NotificationManagerService extends SystemService {
            }
            @Override
            public void updateAutogroupSummary(int userId, String pkg, boolean needsOngoingFlag) {
            public void updateAutogroupSummary(int userId, String pkg, int flags) {
                boolean isAppForeground = pkg != null
                        && mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
                synchronized (mNotificationLock) {
                    updateAutobundledSummaryFlags(userId, pkg, needsOngoingFlag, isAppForeground);
                    updateAutobundledSummaryFlags(userId, pkg, flags, isAppForeground);
                }
            }
        });
@@ -5961,19 +5962,6 @@ public class NotificationManagerService extends SystemService {
        r.addAdjustment(adjustment);
    }
    @VisibleForTesting
    void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
            boolean needsOngoingFlag) {
        NotificationRecord r = createAutoGroupSummary(
                userId, pkg, triggeringKey, needsOngoingFlag);
        if (r != null) {
            final boolean isAppForeground =
                    mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
            mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground,
                    SystemClock.elapsedRealtime()));
        }
    }
    // Clears the 'fake' auto-group summary.
    @VisibleForTesting
    @GuardedBy("mNotificationLock")
@@ -5997,7 +5985,7 @@ public class NotificationManagerService extends SystemService {
    // Creates a 'fake' summary for a package that has exceeded the solo-notification limit.
    NotificationRecord createAutoGroupSummary(int userId, String pkg, String triggeringKey,
            boolean needsOngoingFlag) {
            int flagsToSet) {
        NotificationRecord summaryRecord = null;
        boolean isPermissionFixed = mPermissionHelper.isPermissionFixed(pkg, userId);
        synchronized (mNotificationLock) {
@@ -6007,7 +5995,6 @@ public class NotificationManagerService extends SystemService {
                // adjustment will post a summary if needed.
                return null;
            }
            NotificationChannel channel = notificationRecord.getChannel();
            final StatusBarNotification adjustedSbn = notificationRecord.getSbn();
            userId = adjustedSbn.getUser().getIdentifier();
            int uid =  adjustedSbn.getUid();
@@ -6030,11 +6017,8 @@ public class NotificationManagerService extends SystemService {
                                .setGroupSummary(true)
                                .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
                                .setGroup(GroupHelper.AUTOGROUP_KEY)
                                .setFlag(FLAG_AUTOGROUP_SUMMARY, true)
                                .setFlag(Notification.FLAG_GROUP_SUMMARY, true)
                                .setFlag(FLAG_ONGOING_EVENT, needsOngoingFlag)
                                .setFlag(flagsToSet, true)
                                .setColor(adjustedSbn.getNotification().color)
                                .setLocalOnly(true)
                                .build();
                summaryNotification.extras.putAll(extras);
                Intent appIntent = getContext().getPackageManager().getLaunchIntentForPackage(pkg);
@@ -6372,6 +6356,7 @@ public class NotificationManagerService extends SystemService {
     * The private API only accessible to the system process.
     */
    private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() {
        @Override
        public NotificationChannel getNotificationChannel(String pkg, int uid, String
                channelId) {
@@ -7827,18 +7812,17 @@ public class NotificationManagerService extends SystemService {
                    if (notification.getSmallIcon() != null) {
                        StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
                        mListeners.notifyPostedLocked(r, old);
                        if ((oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup()))
                                && !isCritical(r)) {
                        if (oldSbn == null
                                || !Objects.equals(oldSbn.getGroup(), n.getGroup())
                                || oldSbn.getNotification().flags != n.getNotification().flags) {
                            if (!isCritical(r)) {
                                mHandler.post(() -> {
                                    synchronized (mNotificationLock) {
                                        mGroupHelper.onNotificationPosted(
                                                n, hasAutoGroupSummaryLocked(n));
                                    }
                                });
                        } else if (oldSbn != null) {
                            final NotificationRecord finalRecord = r;
                            mHandler.post(() ->
                                    mGroupHelper.onNotificationUpdated(finalRecord.getSbn()));
                            }
                        }
                    } else {
                        Slog.e(TAG, "Not posting notification without small icon: " + notification);
+344 −149

File changed.

Preview size limit exceeded, changes collapsed.

+56 −27

File changed.

Preview size limit exceeded, changes collapsed.

+35 −0
Original line number Diff line number Diff line
@@ -97,6 +97,41 @@ public class NotificationRecordExtractorDataTest extends UiServiceTestCase {
        assertTrue(extractorData.hasDiffForLoggingLocked(r, 1));
    }

    @Test
    public void testHasDiffs_autoBundled() {
        NotificationRecord r = generateRecord();

        NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData(
                1,
                r.getPackageVisibilityOverride(),
                r.canShowBadge(),
                r.canBubble(),
                r.getNotification().isBubbleNotification(),
                r.getChannel(),
                r.getGroupKey(),
                r.getPeopleOverride(),
                r.getSnoozeCriteria(),
                r.getUserSentiment(),
                r.getSuppressedVisualEffects(),
                r.getSystemGeneratedSmartActions(),
                r.getSmartReplies(),
                r.getImportance(),
                r.getRankingScore(),
                r.isConversation(),
                r.getProposedImportance(),
                r.hasSensitiveContent());

        Bundle signals = new Bundle();
        signals.putString(Adjustment.KEY_GROUP_KEY, "ranker_group");
        Adjustment adjustment = new Adjustment("pkg", r.getKey(), signals, "", 0);
        r.addAdjustment(adjustment);
        NotificationAdjustmentExtractor adjustmentExtractor = new NotificationAdjustmentExtractor();
        adjustmentExtractor.process(r);

        assertTrue(extractorData.hasDiffForRankingLocked(r, 1));
        assertTrue(extractorData.hasDiffForLoggingLocked(r, 1));
    }

    @Test
    public void testHasDiffs_sensitiveContentChange() {
        NotificationRecord r = generateRecord();