diff options
8 files changed, 254 insertions, 64 deletions
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java index 50ae98557de6..ce134e64bf57 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java @@ -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, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt index 933eb207e76d..42bf4e7d0787 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt @@ -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 ) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt index 36c12a719570..387444bec1c0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt @@ -24,47 +24,50 @@ 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( - sectionStyleProvider: SectionStyleProvider, - featureFlags: FeatureFlags, - dataStoreCoordinator: DataStoreCoordinator, - hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator, - hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator, - keyguardCoordinator: KeyguardCoordinator, - rankingCoordinator: RankingCoordinator, - colorizedFgsCoordinator: ColorizedFgsCoordinator, - deviceProvisionedCoordinator: DeviceProvisionedCoordinator, - bubbleCoordinator: BubbleCoordinator, - headsUpCoordinator: HeadsUpCoordinator, - gutsCoordinator: GutsCoordinator, - conversationCoordinator: ConversationCoordinator, - debugModeCoordinator: DebugModeCoordinator, - groupCountCoordinator: GroupCountCoordinator, - groupWhenCoordinator: GroupWhenCoordinator, - mediaCoordinator: MediaCoordinator, - preparationCoordinator: PreparationCoordinator, - remoteInputCoordinator: RemoteInputCoordinator, - rowAlertTimeCoordinator: RowAlertTimeCoordinator, - rowAppearanceCoordinator: RowAppearanceCoordinator, - stackCoordinator: StackCoordinator, - shadeEventCoordinator: ShadeEventCoordinator, - smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator, - viewConfigCoordinator: ViewConfigCoordinator, - visualStabilityCoordinator: VisualStabilityCoordinator, - sensitiveContentCoordinator: SensitiveContentCoordinator, - dismissibilityCoordinator: DismissibilityCoordinator, - dreamCoordinator: DreamCoordinator, - statsLoggerCoordinator: NotificationStatsLoggerCoordinator, +class NotifCoordinatorsImpl +@Inject +constructor( + sectionStyleProvider: SectionStyleProvider, + featureFlags: FeatureFlags, + dataStoreCoordinator: DataStoreCoordinator, + hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator, + hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator, + keyguardCoordinator: KeyguardCoordinator, + rankingCoordinator: RankingCoordinator, + colorizedFgsCoordinator: ColorizedFgsCoordinator, + deviceProvisionedCoordinator: DeviceProvisionedCoordinator, + bubbleCoordinator: BubbleCoordinator, + headsUpCoordinator: HeadsUpCoordinator, + gutsCoordinator: GutsCoordinator, + conversationCoordinator: ConversationCoordinator, + debugModeCoordinator: DebugModeCoordinator, + groupCountCoordinator: GroupCountCoordinator, + groupWhenCoordinator: GroupWhenCoordinator, + mediaCoordinator: MediaCoordinator, + preparationCoordinator: PreparationCoordinator, + remoteInputCoordinator: RemoteInputCoordinator, + rowAlertTimeCoordinator: RowAlertTimeCoordinator, + rowAppearanceCoordinator: RowAppearanceCoordinator, + stackCoordinator: StackCoordinator, + shadeEventCoordinator: ShadeEventCoordinator, + smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator, + viewConfigCoordinator: ViewConfigCoordinator, + visualStabilityCoordinator: VisualStabilityCoordinator, + sensitiveContentCoordinator: SensitiveContentCoordinator, + dismissibilityCoordinator: DismissibilityCoordinator, + dreamCoordinator: DreamCoordinator, + statsLoggerCoordinator: NotificationStatsLoggerCoordinator, ) : NotifCoordinators { private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList() @@ -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,10 +165,11 @@ 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) { - dump("core coordinators", mCoreCoordinators) - dump("normal coordinators", mCoordinators) - } + override fun dumpPipeline(d: PipelineDumper) = + with(d) { + dump("core coordinators", mCoreCoordinators) + dump("normal coordinators", mCoordinators) + } companion object { private const val TAG = "NotifCoordinators" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java index 350e88ea9df5..caa6c17ac3d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt index 5c844bcf749c..e2c9e02672d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt @@ -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. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt index 1f7ab962c3f2..42828d99c7e4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt @@ -17,7 +17,9 @@ package com.android.systemui.statusbar.notification.domain.interactor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype import javax.inject.Inject import kotlinx.coroutines.flow.StateFlow @@ -36,4 +38,15 @@ constructor( fun setHasFilteredOutSeenNotifications(value: Boolean) { notificationListRepository.hasFilteredOutSeenNotifications.value = value } + + /** Set the entry that is identified as the top unseen notification. */ + fun setTopUnseenNotification(entry: NotificationEntry?) { + if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return + notificationListRepository.topUnseenNotificationKey.value = entry?.key + } + + /** Determine if the given notification is the top unseen notification. */ + fun isTopUnseenNotification(entry: NotificationEntry?): Boolean = + if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false + else entry != null && notificationListRepository.topUnseenNotificationKey.value == entry.key } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt index 8889a10f5ad1..bf37036ee018 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt @@ -24,6 +24,11 @@ import com.android.systemui.flags.RefactorFlagUtils /** Helper for reading or using the minimalism prototype flag state. */ @Suppress("NOTHING_TO_INLINE") object NotificationMinimalismPrototype { + + val version: Int by lazy { + SystemProperties.getInt("persist.notification_minimalism_prototype.version", 2) + } + object V1 { /** The aconfig flag name */ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE @@ -35,7 +40,7 @@ object NotificationMinimalismPrototype { /** Is the heads-up cycling animation enabled */ @JvmStatic inline val isEnabled - get() = Flags.notificationMinimalismPrototype() + get() = Flags.notificationMinimalismPrototype() && version == 1 /** * the prototype will now show seen notifications on the locked shade by default, but this @@ -76,4 +81,45 @@ object NotificationMinimalismPrototype { @JvmStatic inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) } + object V2 { + const val FLAG_NAME = Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the heads-up cycling animation enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.notificationMinimalismPrototype() && version == 2 + + /** + * The prototype will (by default) use a promoter to ensure that the top unseen notification + * is not grouped, but this property read allows that behavior to be disabled. + */ + val ungroupTopUnseen: Boolean + get() = + if (isUnexpectedlyInLegacyMode()) false + else + SystemProperties.getBoolean( + "persist.notification_minimalism_prototype.ungroup_top_unseen", + true + ) + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an + * eng build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception + * if the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt index a44674542b5c..749c241ce00c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt @@ -72,6 +72,9 @@ constructor( */ private var maxNotificationsExcludesMedia = false + /** Whether we allow keyguard to show less important notifications above the shelf. */ + private var limitLockScreenToImportant = false + /** Minimum space between two notifications, see [calculateGapAndDividerHeight]. */ private var dividerHeight by notNull<Float>() @@ -85,6 +88,14 @@ constructor( updateResources() } + private fun allowedByPolicy(stackHeight: StackHeight): Boolean = + if (limitLockScreenToImportant && stackHeight.includesLessImportantNotification) { + log { "\tallowedByPolicy = false" } + false + } else { + true + } + /** * Returns whether notifications and (shelf if visible) can fit in total space available. * [shelfSpace] is extra vertical space allowed for the shelf to overlap the lock icon. @@ -183,11 +194,12 @@ constructor( log { "\tGet maxNotifWithoutSavingSpace ---" } val maxNotifWithoutSavingSpace = stackHeightSequence.lastIndexWhile { heightResult -> - canStackFitInSpace( - heightResult, - notifSpace = notifSpace, - shelfSpace = shelfSpace - ) == FitResult.FIT + allowedByPolicy(heightResult) && + canStackFitInSpace( + heightResult, + notifSpace = notifSpace, + shelfSpace = shelfSpace + ) == FitResult.FIT } // How many notifications we can show at heightWithoutLockscreenConstraints @@ -212,11 +224,12 @@ constructor( saveSpaceOnLockscreen = true maxNotifications = stackHeightSequence.lastIndexWhile { heightResult -> - canStackFitInSpace( - heightResult, - notifSpace = notifSpace, - shelfSpace = shelfSpace - ) != FitResult.NO_FIT + allowedByPolicy(heightResult) && + canStackFitInSpace( + heightResult, + notifSpace = notifSpace, + shelfSpace = shelfSpace + ) != FitResult.NO_FIT } log { "\t--- maxNotifications=$maxNotifications" } } @@ -318,7 +331,10 @@ constructor( // Float height of shelf (0 if shelf is not showing), and space before the shelf that // changes during the lockscreen <=> full shade transition. - val shelfHeightWithSpaceBefore: Float + val shelfHeightWithSpaceBefore: Float, + + /** Whether this stack height includes less at least one important notification. */ + val includesLessImportantNotification: Boolean ) private fun computeHeightPerNotificationLimit( @@ -331,12 +347,15 @@ constructor( var previous: ExpandableView? = null val onLockscreen = onLockscreen() + var includesLessImportantNotification = false + // Only shelf. This should never happen, since we allow 1 view minimum (EmptyViewState). yield( StackHeight( notifsHeight = 0f, notifsHeightSavingSpace = 0f, - shelfHeightWithSpaceBefore = shelfHeight + shelfHeightWithSpaceBefore = shelfHeight, + includesLessImportantNotification = includesLessImportantNotification, ) ) @@ -362,6 +381,18 @@ constructor( spaceBeforeShelf + shelfHeight } + if (limitLockScreenToImportant && !includesLessImportantNotification) { + val bucket = (currentNotification as? ExpandableNotificationRow)?.entry?.bucket + includesLessImportantNotification = + when (bucket) { + null, + BUCKET_MEDIA_CONTROLS, + BUCKET_HEADS_UP, + BUCKET_FOREGROUND_SERVICE -> false + else -> true + } + } + log { "\tcomputeHeightPerNotificationLimit i=$i notifs=$notifications " + "notifsHeightSavingSpace=$notifsWithCollapsedHun" + @@ -371,7 +402,8 @@ constructor( StackHeight( notifsHeight = notifications, notifsHeightSavingSpace = notifsWithCollapsedHun, - shelfHeightWithSpaceBefore = shelfWithSpaceBefore + shelfHeightWithSpaceBefore = shelfWithSpaceBefore, + includesLessImportantNotification = includesLessImportantNotification, ) ) } @@ -382,11 +414,16 @@ constructor( infiniteIfNegative( if (NotificationMinimalismPrototype.V1.isEnabled) { NotificationMinimalismPrototype.V1.maxNotifs + } else if (NotificationMinimalismPrototype.V2.isEnabled) { + 1 } else { resources.getInteger(R.integer.keyguard_max_notification_count) } ) - maxNotificationsExcludesMedia = NotificationMinimalismPrototype.V1.isEnabled + maxNotificationsExcludesMedia = + NotificationMinimalismPrototype.V1.isEnabled || + NotificationMinimalismPrototype.V2.isEnabled + limitLockScreenToImportant = NotificationMinimalismPrototype.V2.isEnabled dividerHeight = max(1f, resources.getDimensionPixelSize(R.dimen.notification_divider_height).toFloat()) |