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

Commit 5f6a4ead authored by Jeff DeCew's avatar Jeff DeCew
Browse files

Notification minimalism prototype update

* Now promotes both the top colorized ongoing notification and the top unseen notification. This means the max number of notifications can be 2.
* This is done by adding a second new sectioner used by this prototype for selecting the top ongoing notification.
* This also defines 2 new buckets, since top ongoing and top unseen are now new buckets separate from existing ones.
* This also excludes silent notifications from ever being promoted.

Bug: 330387368
Test: atest SystemUITests
Flag: com.android.systemui.notification_minimalism_prototype
Change-Id: I824db2a924f2cb528ecf18b1a417ef88e7a0d2be
parent 6133ac12
Loading
Loading
Loading
Loading
+35 −28
Original line number Diff line number Diff line
@@ -18,33 +18,29 @@ package com.android.systemui.statusbar.notification

import android.content.Context
import android.provider.DeviceConfig

import com.android.internal.annotations.VisibleForTesting
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
import com.android.systemui.statusbar.notification.stack.BUCKET_MEDIA_CONTROLS
import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
import com.android.systemui.statusbar.notification.stack.BUCKET_PRIORITY_PEOPLE
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.notification.stack.PriorityBucket
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.Utils

import javax.inject.Inject

private var sUsePeopleFiltering: Boolean? = null

/**
 * Feature controller for the NOTIFICATIONS_USE_PEOPLE_FILTERING config.
 */
/** Feature controller for the NOTIFICATIONS_USE_PEOPLE_FILTERING config. */
@SysUISingleton
class NotificationSectionsFeatureManager @Inject constructor(
    val proxy: DeviceConfigProxy,
    val context: Context
) {
class NotificationSectionsFeatureManager
@Inject
constructor(val proxy: DeviceConfigProxy, val context: Context) {

    fun isFilteringEnabled(): Boolean {
        return usePeopleFiltering(proxy)
@@ -55,30 +51,37 @@ class NotificationSectionsFeatureManager @Inject constructor(
    }

    fun getNotificationBuckets(): IntArray {
        if (PriorityPeopleSection.isEnabled) {
        if (PriorityPeopleSection.isEnabled || NotificationMinimalismPrototype.V2.isEnabled) {
            // We don't need this list to be adaptive, it can be the superset of all features.
            return intArrayOf(
                    BUCKET_MEDIA_CONTROLS,
            return PriorityBucket.getAllInOrder()
        }
        return when {
            isFilteringEnabled() && isMediaControlsEnabled() ->
                intArrayOf(
                    BUCKET_HEADS_UP,
                    BUCKET_FOREGROUND_SERVICE,
                    BUCKET_PRIORITY_PEOPLE,
                    BUCKET_MEDIA_CONTROLS,
                    BUCKET_PEOPLE,
                    BUCKET_ALERTING,
                    BUCKET_SILENT,
                    BUCKET_SILENT
                )
        }
        return when {
            isFilteringEnabled() && isMediaControlsEnabled() ->
                intArrayOf(BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, BUCKET_MEDIA_CONTROLS,
                        BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT)
            !isFilteringEnabled() && isMediaControlsEnabled() ->
                intArrayOf(BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, BUCKET_MEDIA_CONTROLS,
                        BUCKET_ALERTING, BUCKET_SILENT)
                intArrayOf(
                    BUCKET_HEADS_UP,
                    BUCKET_FOREGROUND_SERVICE,
                    BUCKET_MEDIA_CONTROLS,
                    BUCKET_ALERTING,
                    BUCKET_SILENT
                )
            isFilteringEnabled() && !isMediaControlsEnabled() ->
                intArrayOf(BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, BUCKET_PEOPLE,
                        BUCKET_ALERTING, BUCKET_SILENT)
            else ->
                intArrayOf(BUCKET_ALERTING, BUCKET_SILENT)
                intArrayOf(
                    BUCKET_HEADS_UP,
                    BUCKET_FOREGROUND_SERVICE,
                    BUCKET_PEOPLE,
                    BUCKET_ALERTING,
                    BUCKET_SILENT
                )
            else -> intArrayOf(BUCKET_ALERTING, BUCKET_SILENT)
        }
    }

@@ -94,8 +97,12 @@ class NotificationSectionsFeatureManager @Inject constructor(

private fun usePeopleFiltering(proxy: DeviceConfigProxy): Boolean {
    if (sUsePeopleFiltering == null) {
        sUsePeopleFiltering = proxy.getBoolean(
                DeviceConfig.NAMESPACE_SYSTEMUI, NOTIFICATIONS_USE_PEOPLE_FILTERING, true)
        sUsePeopleFiltering =
            proxy.getBoolean(
                DeviceConfig.NAMESPACE_SYSTEMUI,
                NOTIFICATIONS_USE_PEOPLE_FILTERING,
                true
            )
    }

    return sUsePeopleFiltering!!
+18 −13
Original line number Diff line number Diff line
@@ -60,22 +60,27 @@ public class ColorizedFgsCoordinator implements Coordinator {
        public boolean isInSection(ListEntry entry) {
            NotificationEntry notificationEntry = entry.getRepresentativeEntry();
            if (notificationEntry != null) {
                return isColorizedForegroundService(notificationEntry) || isCall(notificationEntry);
                return isRichOngoing(notificationEntry);
            }
            return false;
        }
    };

    /** Determines if the given notification is a colorized or call notification */
    public static boolean isRichOngoing(NotificationEntry entry) {
        return isColorizedForegroundService(entry) || isCall(entry);
    }

        private boolean isColorizedForegroundService(NotificationEntry entry) {
    private static boolean isColorizedForegroundService(NotificationEntry entry) {
        Notification notification = entry.getSbn().getNotification();
        return notification.isForegroundService()
                && notification.isColorized()
                && entry.getImportance() > IMPORTANCE_MIN;
    }

        private boolean isCall(NotificationEntry entry) {
    private static boolean isCall(NotificationEntry entry) {
        Notification notification = entry.getSbn().getNotification();
        return entry.getImportance() > IMPORTANCE_MIN
                && notification.isStyle(Notification.CallStyle.class);
    }
    };
}
+39 −18
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@

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

import android.app.NotificationManager
import android.os.UserHandle
import android.provider.Settings
import androidx.annotation.VisibleForTesting
@@ -44,7 +45,8 @@ import com.android.systemui.statusbar.notification.collection.provider.SectionHe
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_ONGOING
import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_UNSEEN
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.headsUpEvents
import com.android.systemui.util.asIndenting
@@ -113,7 +115,7 @@ constructor(
    private fun attachUnseenFilter(pipeline: NotifPipeline) {
        if (NotificationMinimalismPrototype.V2.isEnabled) {
            pipeline.addPromoter(unseenNotifPromoter)
            pipeline.addOnBeforeTransformGroupsListener(::pickOutTopUnseenNotif)
            pipeline.addOnBeforeTransformGroupsListener(::pickOutTopUnseenNotifs)
        }
        pipeline.addFinalizeFilter(unseenNotifFilter)
        pipeline.addCollectionListener(collectionListener)
@@ -347,15 +349,16 @@ constructor(
            }
        }

    private fun pickOutTopUnseenNotif(list: List<ListEntry>) {
    private fun pickOutTopUnseenNotifs(list: List<ListEntry>) {
        if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return
        // Only ever elevate a top unseen notification on keyguard, not even locked shade
        if (statusBarStateController.state != StatusBarState.KEYGUARD) {
            seenNotificationsInteractor.setTopOngoingNotification(null)
            seenNotificationsInteractor.setTopUnseenNotification(null)
            return
        }
        // On keyguard pick the top-ranked unseen or ongoing notification to elevate
        seenNotificationsInteractor.setTopUnseenNotification(
        val nonSummaryEntries: Sequence<NotificationEntry> =
            list
                .asSequence()
                .flatMap {
@@ -365,7 +368,15 @@ constructor(
                        else -> error("unhandled type of $it")
                    }
                }
                .filter { shouldIgnoreUnseenCheck(it) || it in unseenNotifications }
                .filter { it.importance >= NotificationManager.IMPORTANCE_DEFAULT }
        seenNotificationsInteractor.setTopOngoingNotification(
            nonSummaryEntries
                .filter { ColorizedFgsCoordinator.isRichOngoing(it) }
                .minByOrNull { it.ranking.rank }
        )
        seenNotificationsInteractor.setTopUnseenNotification(
            nonSummaryEntries
                .filter { !ColorizedFgsCoordinator.isRichOngoing(it) && it in unseenNotifications }
                .minByOrNull { it.ranking.rank }
        )
    }
@@ -375,29 +386,39 @@ constructor(
        object : NotifPromoter("$TAG-unseen") {
            override fun shouldPromoteToTopLevel(child: NotificationEntry): Boolean =
                if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false
                else if (!NotificationMinimalismPrototype.V2.ungroupTopUnseen) false
                else
                    seenNotificationsInteractor.isTopUnseenNotification(child) &&
                        NotificationMinimalismPrototype.V2.ungroupTopUnseen
                    seenNotificationsInteractor.isTopOngoingNotification(child) ||
                        seenNotificationsInteractor.isTopUnseenNotification(child)
        }

    val unseenNotifSectioner =
        object : NotifSectioner("Unseen", BUCKET_FOREGROUND_SERVICE) {
    val topOngoingSectioner =
        object : NotifSectioner("TopOngoing", BUCKET_TOP_ONGOING) {
            override fun isInSection(entry: ListEntry): Boolean {
                if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false
                if (
                    seenNotificationsInteractor.isTopUnseenNotification(entry.representativeEntry)
                ) {
                    return true
                return entry.anyEntry { notificationEntry ->
                    seenNotificationsInteractor.isTopOngoingNotification(notificationEntry)
                }
                if (entry !is GroupEntry) {
                    return false
            }
                return entry.children.any {
                    seenNotificationsInteractor.isTopUnseenNotification(it)
        }

    val topUnseenSectioner =
        object : NotifSectioner("TopUnseen", BUCKET_TOP_UNSEEN) {
            override fun isInSection(entry: ListEntry): Boolean {
                if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false
                return entry.anyEntry { notificationEntry ->
                    seenNotificationsInteractor.isTopUnseenNotification(notificationEntry)
                }
            }
        }

    private fun ListEntry.anyEntry(predicate: (NotificationEntry?) -> Boolean) =
        when {
            predicate(representativeEntry) -> true
            this !is GroupEntry -> false
            else -> children.any(predicate)
        }

    @VisibleForTesting
    internal val unseenNotifFilter =
        object : NotifFilter("$TAG-unseen") {
+5 −2
Original line number Diff line number Diff line
@@ -116,11 +116,14 @@ constructor(
        }

        // Manually add Ordered Sections
        if (NotificationMinimalismPrototype.V2.isEnabled) {
            mOrderedSections.add(keyguardCoordinator.topOngoingSectioner) // Top Ongoing
        }
        mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
        mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService
        if (NotificationMinimalismPrototype.V2.isEnabled) {
            mOrderedSections.add(keyguardCoordinator.unseenNotifSectioner) // Unseen (FGS)
            mOrderedSections.add(keyguardCoordinator.topUnseenSectioner) // Top Unseen
        }
        mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService
        if (PriorityPeopleSection.isEnabled) {
            mOrderedSections.add(conversationCoordinator.priorityPeopleSectioner) // Priority People
        }
+4 −0
Original line number Diff line number Diff line
@@ -42,6 +42,9 @@ class ActiveNotificationListRepository @Inject constructor() {
    /** Stats about the list of notifications attached to the shade */
    val notifStats = MutableStateFlow(NotifStats.empty)

    /** The key of the top ongoing notification */
    val topOngoingNotificationKey = MutableStateFlow<String?>(null)

    /** The key of the top unseen notification */
    val topUnseenNotificationKey = MutableStateFlow<String?>(null)
}
@@ -75,6 +78,7 @@ data class ActiveNotificationsStore(
    /** Unique key identifying an [ActiveNotificationEntryModel] in the store. */
    sealed class Key {
        data class Individual(val key: String) : Key()

        data class Group(val key: String) : Key()
    }

Loading