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

Commit 6afb4824 authored by András Kurucz's avatar András Kurucz
Browse files

Dismiss Ongoing Notifications

Allow to individually dismissing Ongoing Notifications while the device
is unlocked. Notifications marked with FLAG_NO_DISMISS are exempted from
this feature.

This is behind the flag: SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING

Bug: 240553971
Test: enable the flag, post some ongoing notifications, try to dismiss them from the locked and unlocked state
Test: atest BubblesTest DismissibilityCoordinatorTest ExpandableNotificationRowTest

Change-Id: I41e312421a88b6d867df61c279166b7181dd614e
parent 61a81e5e
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -297,6 +297,16 @@ public class StatusBarNotification implements Parcelable {
        return (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0;
    }

    /**
     * @hide
     *
     * Convenience method to check the notification's flags for
     * {@link Notification#FLAG_NO_DISMISS}.
     */
    public boolean isNonDismissable() {
        return (notification.flags & Notification.FLAG_NO_DISMISS) != 0;
    }

    /**
     * Convenience method to check the notification's flags for
     * either {@link Notification#FLAG_ONGOING_EVENT} or
+11 −2
Original line number Diff line number Diff line
@@ -96,6 +96,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import com.android.systemui.statusbar.notification.collection.notifcollection.RankingAppliedEvent;
import com.android.systemui.statusbar.notification.collection.notifcollection.RankingUpdatedEvent;
import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
import com.android.systemui.util.Assert;
import com.android.systemui.util.time.SystemClock;

@@ -151,6 +152,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
    private final LogBufferEulogizer mEulogizer;
    private final DumpManager mDumpManager;
    private final NotifCollectionInconsistencyTracker mInconsistencyTracker;
    private final NotificationDismissibilityProvider mDismissibilityProvider;

    private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>();
    private final Collection<NotificationEntry> mReadOnlyNotificationSet =
@@ -178,7 +180,8 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
            @Main Handler mainHandler,
            @Background Executor bgExecutor,
            LogBufferEulogizer logBufferEulogizer,
            DumpManager dumpManager) {
            DumpManager dumpManager,
            NotificationDismissibilityProvider dismissibilityProvider) {
        mStatusBarService = statusBarService;
        mClock = clock;
        mNotifPipelineFlags = notifPipelineFlags;
@@ -188,6 +191,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
        mEulogizer = logBufferEulogizer;
        mDumpManager = dumpManager;
        mInconsistencyTracker = new NotifCollectionInconsistencyTracker(mLogger);
        mDismissibilityProvider = dismissibilityProvider;
    }

    /** Initializes the NotifCollection and registers it to receive notification events. */
@@ -554,6 +558,10 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
                .findFirst().orElse(null);
    }

    private boolean isDismissable(NotificationEntry entry) {
        return mDismissibilityProvider.isDismissable(entry);
    }

    /**
     * Checks if the entry is the only child in the logical group;
     * it need not have a summary to qualify
@@ -1006,6 +1014,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
    public class FutureDismissal implements Runnable {
        private final NotificationEntry mEntry;
        private final DismissedByUserStatsCreator mStatsCreator;

        @Nullable
        private final NotificationEntry mSummaryToDismiss;
        private final String mLabel;
@@ -1030,7 +1039,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
            if (isOnlyChildInGroup(entry)) {
                String group = entry.getSbn().getGroupKey();
                NotificationEntry summary = getGroupSummary(group);
                if (summary != null && summary.isDismissable()) return summary;
                if (summary != null && isDismissable(summary)) return summary;
            }
            return null;
        }
+17 −1
Original line number Diff line number Diff line
@@ -730,13 +730,29 @@ public final class NotificationEntry extends ListEntry {
        return true;
    }

    /**
     * Determines whether the NotificationEntry is dismissable based on the Notification flags and
     * the given state. It doesn't recurse children or depend on the view attach state.
     *
     * @param isLocked if the device is locked or unlocked
     * @return true if this NotificationEntry is dismissable.
     */
    public boolean isDismissableForState(boolean isLocked) {
        if (mSbn.isNonDismissable()) {
            // don't dismiss exempted Notifications
            return false;
        }
        // don't dismiss ongoing Notifications when the device is locked
        return !mSbn.isOngoing() || !isLocked;
    }

    /**
     * @return Can the underlying notification be individually dismissed?
     * @see #canViewBeDismissed()
     */
    // TODO: This logic doesn't belong on NotificationEntry. It should be moved to a controller
    // that can be added as a dependency to any class that needs to answer this question.
    public boolean isDismissable() {
    public boolean legacyIsDismissableRecursive() {
        if  (mSbn.isOngoing()) {
            return false;
        }
+81 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.statusbar.notification.collection.coordinator

import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProviderImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject

/** Decides if a Notification can be dismissed by the user. */
@CoordinatorScope
class DismissibilityCoordinator
@Inject
constructor(
    private val keyguardStateController: KeyguardStateController,
    private val provider: NotificationDismissibilityProviderImpl
) : Coordinator {

    override fun attach(pipeline: NotifPipeline) {
        pipeline.addOnBeforeRenderListListener(::onBeforeRenderListListener)
    }

    private fun onBeforeRenderListListener(entries: List<ListEntry>) {
        val isLocked = !keyguardStateController.isUnlocked
        val nonDismissableEntryKeys = mutableSetOf<String>()
        markNonDismissibleEntries(nonDismissableEntryKeys, entries, isLocked)
        provider.update(nonDismissableEntryKeys)
    }

    /**
     * Visits every entry and its children to mark the dismissible entries.
     * @param markedKeys set to store the marked entry keys
     * @param entries to visit
     * @param isLocked the locked state of the device
     * @return true if any of the entries were marked as non-dismissible.
     */
    private fun markNonDismissibleEntries(
        markedKeys: MutableSet<String>,
        entries: List<ListEntry>,
        isLocked: Boolean
    ): Boolean {
        var anyNonDismissableEntries = false

        for (entry in entries) {
            entry.representativeEntry?.let { notifEntry ->
                // mark the entry if it is non-dismissible
                if (!notifEntry.isDismissableForState(isLocked)) {
                    markedKeys.add(notifEntry.key)
                    anyNonDismissableEntries = true
                }
            }

            if (entry is GroupEntry) {
                if (markNonDismissibleEntries(markedKeys, entry.children, isLocked)) {
                    // if any child is non-dismissible, mark the parent as well
                    entry.representativeEntry?.let { markedKeys.add(it.key) }
                    anyNonDismissableEntries = true
                }
            }
        }

        return anyNonDismissableEntries
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ class NotifCoordinatorsImpl @Inject constructor(
    viewConfigCoordinator: ViewConfigCoordinator,
    visualStabilityCoordinator: VisualStabilityCoordinator,
    sensitiveContentCoordinator: SensitiveContentCoordinator,
    dismissibilityCoordinator: DismissibilityCoordinator
) : NotifCoordinators {

    private val mCoordinators: MutableList<Coordinator> = ArrayList()
@@ -93,6 +94,7 @@ class NotifCoordinatorsImpl @Inject constructor(
        mCoordinators.add(gutsCoordinator)
        mCoordinators.add(preparationCoordinator)
        mCoordinators.add(remoteInputCoordinator)
        mCoordinators.add(dismissibilityCoordinator)

        // Manually add Ordered Sections
        // HeadsUp > FGS > People > Alerting > Silent > Minimized > Unknown/Default
Loading