diff options
11 files changed, 179 insertions, 9 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index dd4af7bb780e..53ddcfaf9576 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -20,6 +20,7 @@ import android.content.testableContext import android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.internal.logging.uiEventLoggerFake import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository @@ -39,13 +40,22 @@ import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runCurrent +import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.res.R +import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.shared.model.Overlays +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.transitionState import com.android.systemui.testKosmos import com.android.systemui.util.settings.fakeGlobalSettings import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -368,6 +378,76 @@ class BouncerInteractorTest : SysuiTestCase() { testableResources.removeOverride(R.bool.can_use_one_handed_bouncer) } + @Test + fun bouncerExpansion_lockscreenToBouncer() = + kosmos.runTest { + val bouncerExpansion by collectLastValue(underTest.bouncerExpansion) + + val progress = MutableStateFlow(0f) + kosmos.sceneContainerRepository.setTransitionState(transitionState) + transitionState.value = + ObservableTransitionState.Transition.showOverlay( + overlay = Overlays.Bouncer, + fromScene = Scenes.Lockscreen, + currentOverlays = flowOf(emptySet()), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + + assertThat(bouncerExpansion).isEqualTo(0f) + + progress.value = 1f + assertThat(bouncerExpansion).isEqualTo(1f) + } + + @Test + fun bouncerExpansion_BouncerToLockscreen() = + kosmos.runTest { + val bouncerExpansion by collectLastValue(underTest.bouncerExpansion) + + val progress = MutableStateFlow(0f) + kosmos.sceneContainerRepository.setTransitionState(transitionState) + transitionState.value = + ObservableTransitionState.Transition.hideOverlay( + overlay = Overlays.Bouncer, + toScene = Scenes.Lockscreen, + currentOverlays = flowOf(emptySet()), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + + assertThat(bouncerExpansion).isEqualTo(1f) + + progress.value = 1f + assertThat(bouncerExpansion).isEqualTo(0f) + } + + @Test + fun bouncerExpansion_shadeToLockscreenUnderBouncer() = + kosmos.runTest { + val bouncerExpansion by collectLastValue(underTest.bouncerExpansion) + + val progress = MutableStateFlow(0f) + kosmos.sceneContainerRepository.setTransitionState(transitionState) + transitionState.value = + ObservableTransitionState.Transition( + fromScene = Scenes.Shade, + toScene = Scenes.Lockscreen, + currentScene = flowOf(Scenes.Lockscreen), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + currentOverlays = setOf(Overlays.Bouncer), + ) + + assertThat(bouncerExpansion).isEqualTo(1f) + + progress.value = 1f + assertThat(bouncerExpansion).isEqualTo(1f) + } + companion object { private const val MESSAGE_ENTER_YOUR_PIN = "Enter your PIN" private const val MESSAGE_ENTER_YOUR_PASSWORD = "Enter your password" diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index 75503e8575f0..b26a2c08532e 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -17,7 +17,9 @@ package com.android.systemui.bouncer.domain.interactor import android.app.StatusBarManager.SESSION_KEYGUARD +import com.android.app.tracing.FlowTracing.traceAsCounter import com.android.app.tracing.coroutines.asyncTraced as async +import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.internal.logging.UiEventLogger import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor @@ -38,9 +40,12 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInt import com.android.systemui.log.SessionTracker import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.domain.interactor.SceneBackInteractor +import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.ShadeDisplayAware +import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -49,7 +54,9 @@ import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map /** Encapsulates business logic and application state accessing use-cases. */ @@ -65,6 +72,7 @@ constructor( private val powerInteractor: PowerInteractor, private val uiEventLogger: UiEventLogger, private val sessionTracker: SessionTracker, + sceneInteractor: SceneInteractor, sceneBackInteractor: SceneBackInteractor, @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor, ) { @@ -149,6 +157,31 @@ constructor( val dismissDestination: Flow<SceneKey> = sceneBackInteractor.backScene.map { it ?: Scenes.Lockscreen } + /** The amount [0-1] that the Bouncer Overlay has been transitioned to. */ + val bouncerExpansion: Flow<Float> = + if (SceneContainerFlag.isEnabled) { + sceneInteractor.transitionState.flatMapLatestConflated { state -> + when (state) { + is ObservableTransitionState.Idle -> + flowOf(if (Overlays.Bouncer in state.currentOverlays) 1f else 0f) + is ObservableTransitionState.Transition -> + if (state.toContent == Overlays.Bouncer) { + state.progress + } else if (state.fromContent == Overlays.Bouncer) { + state.progress.map { progress -> 1 - progress } + } else { + state.currentOverlays().map { + if (Overlays.Bouncer in it) 1f else 0f + } + } + } + } + } else { + flowOf() + } + .distinctUntilChanged() + .traceAsCounter("bouncer_expansion") { (it * 100f).toInt() } + /** Notifies that the user has places down a pointer, not necessarily dragging just yet. */ fun onDown() { falsingInteractor.avoidGesture() diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt index 246177e0c46d..81146852aefc 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt @@ -334,7 +334,7 @@ constructor( } else if (state.fromContent == overlay) { state.progress.map { progress -> 1 - progress } } else { - flowOf(0f) + state.currentOverlays().map { if (overlay in it) 1f else 0f } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 4390f1b16ffd..1a17b8efb4ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -1226,6 +1226,14 @@ public class NotificationStackScrollLayout } @Override + public void setOccluded(boolean isOccluded) { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) { + return; + } + this.setVisibility(isOccluded ? View.INVISIBLE : View.VISIBLE); + } + + @Override public void setScrollState(@NonNull ShadeScrollState scrollState) { if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) { return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt index a7305f7f27ab..9c855e9cd9b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt @@ -49,6 +49,9 @@ interface NotificationScrollView { /** Max alpha for this view */ fun setMaxAlpha(alpha: Float) + /** Set whether this view is occluded by something else. */ + fun setOccluded(isOccluded: Boolean) + /** Sets a clipping shape, which defines the drawable area of this view. */ fun setClippingShape(shape: ShadeScrimShape?) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt index a4e39cbd8388..653344ae9203 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt @@ -86,6 +86,8 @@ constructor( .collectTraced { view.setClippingShape(it) } } + launch { viewModel.isOccluded.collectTraced { view.setOccluded(it) } } + launch { viewModel.maxAlpha.collectTraced { view.setMaxAlpha(it) } } launch { viewModel.shadeScrollState.collect { view.setScrollState(it) } } launch { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt index 1dbaf2f0f401..a277597e23df 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt @@ -24,7 +24,9 @@ import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.ObservableTransitionState.Idle import com.android.compose.animation.scene.ObservableTransitionState.Transition import com.android.compose.animation.scene.ObservableTransitionState.Transition.ChangeScene +import com.android.compose.animation.scene.OverlayKey import com.android.compose.animation.scene.SceneKey +import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.lifecycle.ExclusiveActivatable @@ -70,6 +72,7 @@ constructor( private val stackAppearanceInteractor: NotificationStackAppearanceInteractor, shadeInteractor: ShadeInteractor, shadeModeInteractor: ShadeModeInteractor, + bouncerInteractor: BouncerInteractor, private val remoteInputInteractor: RemoteInputInteractor, private val sceneInteractor: SceneInteractor, // TODO(b/336364825) Remove Lazy when SceneContainerFlag is released - @@ -131,12 +134,15 @@ constructor( private fun expandFractionDuringOverlayTransition( transition: Transition, currentScene: SceneKey, + currentOverlays: Set<OverlayKey>, shadeExpansion: Float, ): Float { return if (currentScene == Scenes.Lockscreen) { 1f } else if (transition.isTransitioningFromOrTo(Overlays.NotificationsShade)) { shadeExpansion + } else if (Overlays.NotificationsShade in currentOverlays) { + 1f } else { 0f } @@ -161,12 +167,13 @@ constructor( shadeInteractor.qsExpansion, shadeModeInteractor.shadeMode, sceneInteractor.transitionState, - ) { shadeExpansion, qsExpansion, _, transitionState -> + sceneInteractor.currentOverlays, + ) { shadeExpansion, qsExpansion, _, transitionState, currentOverlays -> when (transitionState) { is Idle -> if ( expandedInScene(transitionState.currentScene) || - Overlays.NotificationsShade in transitionState.currentOverlays + Overlays.NotificationsShade in currentOverlays ) { 1f } else { @@ -182,12 +189,14 @@ constructor( expandFractionDuringOverlayTransition( transition = transitionState, currentScene = transitionState.currentScene, + currentOverlays = currentOverlays, shadeExpansion = shadeExpansion, ) is Transition.ReplaceOverlay -> expandFractionDuringOverlayTransition( transition = transitionState, currentScene = transitionState.currentScene, + currentOverlays = currentOverlays, shadeExpansion = shadeExpansion, ) } @@ -198,6 +207,12 @@ constructor( val qsExpandFraction: Flow<Float> = shadeInteractor.qsExpansion.dumpWhileCollecting("qsExpandFraction") + val isOccluded: Flow<Boolean> = + bouncerInteractor.bouncerExpansion + .map { it == 1f } + .distinctUntilChanged() + .dumpWhileCollecting("isOccluded") + /** Blur radius to be applied to Notifications. */ fun blurRadius(maxBlurRadius: Flow<Int>) = combine(blurFraction, maxBlurRadius) { fraction, maxRadius -> fraction * maxRadius } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index 33cc62c9a75a..9d55e1d9d592 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -21,6 +21,7 @@ import android.content.Context import androidx.annotation.VisibleForTesting import com.android.app.tracing.coroutines.flow.flowName import com.android.systemui.Flags.glanceableHubV2 +import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor @@ -106,7 +107,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest @@ -132,6 +132,7 @@ constructor( private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val shadeInteractor: ShadeInteractor, + private val bouncerInteractor: BouncerInteractor, shadeModeInteractor: ShadeModeInteractor, notificationStackAppearanceInteractor: NotificationStackAppearanceInteractor, private val alternateBouncerToGoneTransitionViewModel: @@ -516,8 +517,13 @@ constructor( combineTransform( shadeInteractor.shadeExpansion, shadeInteractor.qsExpansion, - ) { shadeExpansion, qsExpansion -> - if (qsExpansion == 1f) { + bouncerInteractor.bouncerExpansion, + ) { shadeExpansion, qsExpansion, bouncerExpansion -> + if (bouncerExpansion == 1f) { + emit(0f) + } else if (bouncerExpansion > 0f) { + emit(1 - bouncerExpansion) + } else if (qsExpansion == 1f) { // Ensure HUNs will be visible in QS shade (at least while // unlocked) emit(1f) @@ -526,19 +532,36 @@ constructor( emit(1f - qsExpansion) } } - Split -> isAnyExpanded.filter { it }.map { 1f } + Split -> + combineTransform(isAnyExpanded, bouncerInteractor.bouncerExpansion) { + isAnyExpanded, + bouncerExpansion -> + if (bouncerExpansion == 1f) { + emit(0f) + } else if (bouncerExpansion > 0f) { + emit(1 - bouncerExpansion) + } else if (isAnyExpanded) { + emit(1f) + } + } Dual -> combineTransform( shadeModeInteractor.isShadeLayoutWide, headsUpNotificationInteractor.get().isHeadsUpOrAnimatingAway, shadeInteractor.shadeExpansion, shadeInteractor.qsExpansion, + bouncerInteractor.bouncerExpansion, ) { isShadeLayoutWide, isHeadsUpOrAnimatingAway, shadeExpansion, - qsExpansion -> - if (isShadeLayoutWide) { + qsExpansion, + bouncerExpansion -> + if (bouncerExpansion == 1f) { + emit(0f) + } else if (bouncerExpansion > 0f) { + emit(1 - bouncerExpansion) + } else if (isShadeLayoutWide) { if (shadeExpansion > 0f) { emit(1f) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt index d27ecce89937..94d27f73aee7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt @@ -28,6 +28,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.log.sessionTracker import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.sceneBackInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor val Kosmos.bouncerInteractor by Fixture { BouncerInteractor( @@ -39,6 +40,7 @@ val Kosmos.bouncerInteractor by Fixture { powerInteractor = powerInteractor, uiEventLogger = uiEventLogger, sessionTracker = sessionTracker, + sceneInteractor = sceneInteractor, sceneBackInteractor = sceneBackInteractor, configurationInteractor = configurationInteractor, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt index 167b11da9dba..87ce501b0402 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel +import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.dump.dumpManager import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos @@ -32,6 +33,7 @@ val Kosmos.notificationScrollViewModel by Fixture { stackAppearanceInteractor = notificationStackAppearanceInteractor, shadeInteractor = shadeInteractor, shadeModeInteractor = shadeModeInteractor, + bouncerInteractor = bouncerInteractor, remoteInputInteractor = remoteInputInteractor, sceneInteractor = sceneInteractor, keyguardInteractor = { keyguardInteractor }, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt index 17ef208fe12e..85fe3d9a44ce 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import android.content.applicationContext +import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.dump.dumpManager @@ -74,6 +75,7 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture { keyguardInteractor = keyguardInteractor, keyguardTransitionInteractor = keyguardTransitionInteractor, shadeInteractor = shadeInteractor, + bouncerInteractor = bouncerInteractor, shadeModeInteractor = shadeModeInteractor, notificationStackAppearanceInteractor = notificationStackAppearanceInteractor, alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel, |