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

Commit db070dd9 authored by Jeff DeCew's avatar Jeff DeCew Committed by Android (Google) Code Review
Browse files

Merge "Implement the V2 notification minimalism prototype" into main

parents 49b84a0c 16921422
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
@@ -31,9 +31,9 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.compose.animation.scene.ObservableTransitionState;
@@ -57,6 +57,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.kotlin.JavaAdapter;
@@ -75,7 +76,7 @@ import org.mockito.MockitoAnnotations;
import org.mockito.verification.VerificationMode;

@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper
public class VisualStabilityCoordinatorTest extends SysuiTestCase {

@@ -86,6 +87,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
    @Mock private WakefulnessLifecycle mWakefulnessLifecycle;
    @Mock private StatusBarStateController mStatusBarStateController;
    @Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener;
    @Mock private SeenNotificationsInteractor mSeenNotificationsInteractor;
    @Mock private HeadsUpManager mHeadsUpManager;
    @Mock private VisibilityLocationProvider mVisibilityLocationProvider;
    @Mock private VisualStabilityProvider mVisualStabilityProvider;
@@ -121,6 +123,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
                mHeadsUpManager,
                mShadeAnimationInteractor,
                mJavaAdapter,
                mSeenNotificationsInteractor,
                mStatusBarStateController,
                mVisibilityLocationProvider,
                mVisualStabilityProvider,
+67 −2
Original line number Diff line number Diff line
@@ -31,15 +31,20 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.expansionChanges
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.NotificationEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
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.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.headsUpEvents
import com.android.systemui.util.asIndenting
@@ -106,6 +111,10 @@ constructor(
    }

    private fun attachUnseenFilter(pipeline: NotifPipeline) {
        if (NotificationMinimalismPrototype.V2.isEnabled) {
            pipeline.addPromoter(unseenNotifPromoter)
            pipeline.addOnBeforeTransformGroupsListener(::pickOutTopUnseenNotif)
        }
        pipeline.addFinalizeFilter(unseenNotifFilter)
        pipeline.addCollectionListener(collectionListener)
        scope.launch { trackUnseenFilterSettingChanges() }
@@ -263,7 +272,10 @@ constructor(
    }

    private fun unseenFeatureEnabled(): Flow<Boolean> {
        if (NotificationMinimalismPrototype.V1.isEnabled) {
        if (
            NotificationMinimalismPrototype.V1.isEnabled ||
                NotificationMinimalismPrototype.V2.isEnabled
        ) {
            return flowOf(true)
        }
        return secureSettings
@@ -334,6 +346,57 @@ constructor(
            }
        }

    private fun pickOutTopUnseenNotif(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.setTopUnseenNotification(null)
            return
        }
        // On keyguard pick the top-ranked unseen or ongoing notification to elevate
        seenNotificationsInteractor.setTopUnseenNotification(
            list
                .asSequence()
                .flatMap {
                    when (it) {
                        is NotificationEntry -> listOfNotNull(it)
                        is GroupEntry -> it.children
                        else -> error("unhandled type of $it")
                    }
                }
                .filter { shouldIgnoreUnseenCheck(it) || it in unseenNotifications }
                .minByOrNull { it.ranking.rank }
        )
    }

    @VisibleForTesting
    internal val unseenNotifPromoter =
        object : NotifPromoter("$TAG-unseen") {
            override fun shouldPromoteToTopLevel(child: NotificationEntry): Boolean =
                if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false
                else
                    seenNotificationsInteractor.isTopUnseenNotification(child) &&
                        NotificationMinimalismPrototype.V2.ungroupTopUnseen
        }

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

    @VisibleForTesting
    internal val unseenNotifFilter =
        object : NotifFilter("$TAG-unseen") {
@@ -351,7 +414,9 @@ constructor(
             * allow seen notifications to appear in the locked shade.
             */
            private fun isOnKeyguard(): Boolean =
                if (
                if (NotificationMinimalismPrototype.V2.isEnabled) {
                    false // disable this feature under this prototype
                } else if (
                    NotificationMinimalismPrototype.V1.isEnabled &&
                        NotificationMinimalismPrototype.V1.showOnLockedShade
                ) {
+54 −43
Original line number Diff line number Diff line
@@ -24,17 +24,20 @@ import com.android.systemui.statusbar.notification.collection.SortBySectionTimeF
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import javax.inject.Inject

/**
 * Handles the attachment of [Coordinator]s to the [NotifPipeline] so that the
 * Coordinators can register their respective callbacks.
 * Handles the attachment of [Coordinator]s to the [NotifPipeline] so that the Coordinators can
 * register their respective callbacks.
 */
interface NotifCoordinators : Coordinator, PipelineDumpable

@CoordinatorScope
class NotifCoordinatorsImpl @Inject constructor(
class NotifCoordinatorsImpl
@Inject
constructor(
    sectionStyleProvider: SectionStyleProvider,
    featureFlags: FeatureFlags,
    dataStoreCoordinator: DataStoreCoordinator,
@@ -114,6 +117,9 @@ class NotifCoordinatorsImpl @Inject constructor(
        // Manually add Ordered Sections
        mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
        mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService
        if (NotificationMinimalismPrototype.V2.isEnabled) {
            mOrderedSections.add(keyguardCoordinator.unseenNotifSectioner) // Unseen (FGS)
        }
        mOrderedSections.add(conversationCoordinator.peopleAlertingSectioner) // People Alerting
        if (!SortBySectionTimeFlag.isEnabled) {
            mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent
@@ -124,22 +130,26 @@ class NotifCoordinatorsImpl @Inject constructor(

        sectionStyleProvider.setMinimizedSections(setOf(rankingCoordinator.minimizedSectioner))
        if (SortBySectionTimeFlag.isEnabled) {
            sectionStyleProvider.setSilentSections(listOf(
            sectionStyleProvider.setSilentSections(
                listOf(
                    rankingCoordinator.silentSectioner,
                    rankingCoordinator.minimizedSectioner,
            ))
                )
            )
        } else {
            sectionStyleProvider.setSilentSections(listOf(
            sectionStyleProvider.setSilentSections(
                listOf(
                    conversationCoordinator.peopleSilentSectioner,
                    rankingCoordinator.silentSectioner,
                    rankingCoordinator.minimizedSectioner,
            ))
                )
            )
        }
    }

    /**
     * Sends the pipeline to each coordinator when the pipeline is ready to accept
     * [Pluggable]s, [NotifCollectionListener]s and [NotifLifetimeExtender]s.
     * Sends the pipeline to each coordinator when the pipeline is ready to accept [Pluggable]s,
     * [NotifCollectionListener]s and [NotifLifetimeExtender]s.
     */
    override fun attach(pipeline: NotifPipeline) {
        for (c in mCoreCoordinators) {
@@ -155,7 +165,8 @@ class NotifCoordinatorsImpl @Inject constructor(
     * As part of the NotifPipeline dumpable, dumps the list of coordinators; sections are omitted
     * as they are dumped in the RenderStageManager instead.
     */
    override fun dumpPipeline(d: PipelineDumper) = with(d) {
    override fun dumpPipeline(d: PipelineDumper) =
        with(d) {
            dump("core coordinators", mCoreCoordinators)
            dump("normal coordinators", mCoordinators)
        }
+14 −2
Original line number Diff line number Diff line
@@ -38,6 +38,8 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.Compile;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -63,6 +65,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
    public static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
    private final DelayableExecutor mDelayableExecutor;
    private final HeadsUpManager mHeadsUpManager;
    private final SeenNotificationsInteractor mSeenNotificationsInteractor;
    private final ShadeAnimationInteractor mShadeAnimationInteractor;
    private final StatusBarStateController mStatusBarStateController;
    private final JavaAdapter mJavaAdapter;
@@ -101,6 +104,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
            HeadsUpManager headsUpManager,
            ShadeAnimationInteractor shadeAnimationInteractor,
            JavaAdapter javaAdapter,
            SeenNotificationsInteractor seenNotificationsInteractor,
            StatusBarStateController statusBarStateController,
            VisibilityLocationProvider visibilityLocationProvider,
            VisualStabilityProvider visualStabilityProvider,
@@ -109,6 +113,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
        mHeadsUpManager = headsUpManager;
        mShadeAnimationInteractor = shadeAnimationInteractor;
        mJavaAdapter = javaAdapter;
        mSeenNotificationsInteractor = seenNotificationsInteractor;
        mVisibilityLocationProvider = visibilityLocationProvider;
        mVisualStabilityProvider = visualStabilityProvider;
        mWakefulnessLifecycle = wakefulnessLifecycle;
@@ -142,8 +147,15 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
    private final NotifStabilityManager mNotifStabilityManager =
            new NotifStabilityManager("VisualStabilityCoordinator") {
                private boolean canMoveForHeadsUp(NotificationEntry entry) {
                    return entry != null && mHeadsUpManager.isHeadsUpEntry(entry.getKey())
                            && !mVisibilityLocationProvider.isInVisibleLocation(entry);
                    if (entry == null) {
                        return false;
                    }
                    boolean isTopUnseen = NotificationMinimalismPrototype.V2.isEnabled()
                            && mSeenNotificationsInteractor.isTopUnseenNotification(entry);
                    if (isTopUnseen || mHeadsUpManager.isHeadsUpEntry(entry.getKey())) {
                        return !mVisibilityLocationProvider.isInVisibleLocation(entry);
                    }
                    return false;
                }

                @Override
+3 −0
Original line number Diff line number Diff line
@@ -41,6 +41,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 unseen notification */
    val topUnseenNotificationKey = MutableStateFlow<String?>(null)
}

/** Represents the notification list, comprised of groups and individual notifications. */
Loading