Loading packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt +14 −3 Original line number Diff line number Diff line Loading @@ -16,11 +16,17 @@ package com.android.systemui.notifications.ui.composable import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.blur import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey Loading @@ -29,7 +35,6 @@ import com.android.compose.animation.scene.UserActionResult import com.android.compose.lifecycle.DisposableEffectWithLifecycle import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.media.remedia.ui.compose.Media import com.android.systemui.media.remedia.ui.compose.MediaPresentationStyle Loading @@ -56,7 +61,6 @@ constructor( private val contentViewModelFactory: NotificationsShadeOverlayContentViewModel.Factory, private val shadeSession: SaveableSession, private val stackScrollView: Lazy<NotificationScrollView>, private val keyguardClockViewModel: KeyguardClockViewModel, private val jankMonitor: InteractionJankMonitor, ) : Overlay { override val key = Overlays.NotificationsShade Loading Loading @@ -92,11 +96,18 @@ constructor( val isFullWidth = isFullWidthShade() val targetBlurRadiusPx: Float by remember(layoutState) { derivedStateOf { viewModel.calculateTargetBlurRadius(layoutState.transitionState) } } val animatedBlurRadiusPx: Float by animateFloatAsState(targetValue = targetBlurRadiusPx, label = "NSOverlay-blurRadius") OverlayShade( panelElement = NotificationsShade.Elements.Panel, alignmentOnWideScreens = viewModel.alignmentOnWideScreens, enableTransparency = viewModel.isTransparencyEnabled, modifier = modifier, modifier = modifier.blur(with(LocalDensity.current) { animatedBlurRadiusPx.toDp() }), onScrimClicked = viewModel::onScrimClicked, onBackgroundPlaced = { bounds, _, _ -> viewModel.onShadeOverlayBoundsChanged(bounds) }, header = { Loading packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt +115 −77 Original line number Diff line number Diff line Loading @@ -27,17 +27,20 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.geometry.Rect import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.content.state.TransitionState import com.android.systemui.Flags.FLAG_NOTIFICATION_SHADE_BLUR import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.domain.interactor.AuthenticationResult import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.desktop.domain.interactor.DesktopInteractor import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.backgroundScope import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher Loading Loading @@ -70,16 +73,11 @@ import com.android.systemui.testKosmos import com.android.systemui.window.data.repository.fakeWindowRootViewBlurRepository import com.android.systemui.window.domain.interactor.windowRootViewBlurInteractor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.update import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper Loading @@ -87,21 +85,19 @@ import org.junit.runner.RunWith class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val testScope = kosmos.testScope private val sceneInteractor by lazy { kosmos.sceneInteractor } private val underTest by lazy { kosmos.notificationsShadeOverlayContentViewModel } private val Kosmos.underTest by Fixture { notificationsShadeOverlayContentViewModel } @Before fun setUp() { kosmos.sceneContainerStartable.start() kosmos.enableDualShade() kosmos.runCurrent() fun setUp() = with(kosmos) { sceneContainerStartable.start() enableDualShade() underTest.activateIn(testScope) } @Test fun showHeader_desktopStatusBarDisabled_true() = testScope.runTest { kosmos.runTest { setUseDesktopStatusBar(false) assertThat(underTest.showHeader).isTrue() } Loading @@ -109,7 +105,7 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test @EnableFlags(StatusBarForDesktop.FLAG_NAME) fun showHeader_desktopStatusBarEnabled_statusBarForDesktopEnabled_false() = testScope.runTest { kosmos.runTest { setUseDesktopStatusBar(true) assertThat(underTest.showHeader).isFalse() } Loading @@ -117,7 +113,7 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test @DisableFlags(StatusBarForDesktop.FLAG_NAME) fun showHeader_desktopStatusBarEnabled_statusBarForDesktopDisabled_true() = testScope.runTest { kosmos.runTest { setUseDesktopStatusBar(true) assertThat(underTest.showHeader).isTrue() } Loading @@ -125,32 +121,29 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test @DisableFlags(StatusBarForDesktop.FLAG_NAME) fun alignmentOnWideScreens_statusBarForDesktopDisabled_topStart() = testScope.runTest { assertThat(kosmos.createTestInstance().alignmentOnWideScreens) .isEqualTo(Alignment.TopStart) kosmos.runTest { assertThat(createTestInstance().alignmentOnWideScreens).isEqualTo(Alignment.TopStart) } @Test @EnableFlags(StatusBarForDesktop.FLAG_NAME) fun alignmentOnWideScreens_configDisabled_statusBarForDesktopEnabled_topStart() = testScope.runTest { kosmos.runTest { overrideConfig(R.bool.config_notificationShadeOnTopEnd, false) assertThat(kosmos.createTestInstance().alignmentOnWideScreens) .isEqualTo(Alignment.TopStart) assertThat(createTestInstance().alignmentOnWideScreens).isEqualTo(Alignment.TopStart) } @Test @EnableFlags(StatusBarForDesktop.FLAG_NAME) fun alignmentOnWideScreens_configEnabled_statusBarForDesktopEnabled_topEnd() = testScope.runTest { kosmos.runTest { overrideConfig(R.bool.config_notificationShadeOnTopEnd, true) assertThat(kosmos.createTestInstance().alignmentOnWideScreens) .isEqualTo(Alignment.TopEnd) assertThat(createTestInstance().alignmentOnWideScreens).isEqualTo(Alignment.TopEnd) } @Test fun onScrimClicked_hidesShade() = testScope.runTest { kosmos.runTest { val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) sceneInteractor.showOverlay(Overlays.NotificationsShade, "test") assertThat(currentOverlays).contains(Overlays.NotificationsShade) Loading @@ -162,7 +155,7 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test fun deviceLocked_hidesShade() = testScope.runTest { kosmos.runTest { val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) unlockDevice() sceneInteractor.showOverlay(Overlays.NotificationsShade, "test") Loading @@ -175,9 +168,9 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test fun shadeNotTouchable_hidesShade() = testScope.runTest { kosmos.runTest { val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val isShadeTouchable by collectLastValue(kosmos.shadeInteractor.isShadeTouchable) val isShadeTouchable by collectLastValue(shadeInteractor.isShadeTouchable) assertThat(isShadeTouchable).isTrue() sceneInteractor.showOverlay(Overlays.NotificationsShade, "test") assertThat(currentOverlays).contains(Overlays.NotificationsShade) Loading @@ -189,40 +182,36 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test fun showMedia_activeMedia_true() = testScope.runTest { kosmos.mediaPipelineRepository.addCurrentUserMediaEntry(MediaData(active = true)) runCurrent() kosmos.runTest { mediaPipelineRepository.addCurrentUserMediaEntry(MediaData(active = true)) assertThat(underTest.showMedia).isTrue() } @Test fun showMedia_InactiveMedia_false() = testScope.runTest { kosmos.mediaPipelineRepository.addCurrentUserMediaEntry(MediaData(active = false)) runCurrent() kosmos.runTest { mediaPipelineRepository.addCurrentUserMediaEntry(MediaData(active = false)) assertThat(underTest.showMedia).isFalse() } @Test fun showMedia_noMedia_false() = testScope.runTest { kosmos.mediaPipelineRepository.addCurrentUserMediaEntry(MediaData(active = true)) kosmos.mediaPipelineRepository.clearCurrentUserMedia() runCurrent() kosmos.runTest { mediaPipelineRepository.addCurrentUserMediaEntry(MediaData(active = true)) mediaPipelineRepository.clearCurrentUserMedia() assertThat(underTest.showMedia).isFalse() } @Test fun showMedia_qsDisabled_false() = testScope.runTest { kosmos.mediaPipelineRepository.addCurrentUserMediaEntry(MediaData(active = true)) kosmos.fakeDisableFlagsRepository.disableFlags.update { kosmos.runTest { mediaPipelineRepository.addCurrentUserMediaEntry(MediaData(active = true)) fakeDisableFlagsRepository.disableFlags.update { it.copy(disable2 = DISABLE2_QUICK_SETTINGS) } runCurrent() assertThat(underTest.showMedia).isFalse() } Loading @@ -230,9 +219,8 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test @DisableFlags(FLAG_NOTIFICATION_SHADE_BLUR) fun transparencyEnabled_shadeBlurFlagOff_isDisabled() = testScope.runTest { kosmos.fakeWindowRootViewBlurRepository.isBlurSupported.value = true runCurrent() kosmos.runTest { fakeWindowRootViewBlurRepository.isBlurSupported.value = true assertThat(underTest.isTransparencyEnabled).isFalse() } Loading @@ -240,9 +228,8 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test @EnableFlags(FLAG_NOTIFICATION_SHADE_BLUR) fun transparencyEnabled_shadeBlurFlagOn_blurSupported_isEnabled() = testScope.runTest { kosmos.fakeWindowRootViewBlurRepository.isBlurSupported.value = true runCurrent() kosmos.runTest { fakeWindowRootViewBlurRepository.isBlurSupported.value = true assertThat(underTest.isTransparencyEnabled).isTrue() } Loading @@ -250,16 +237,72 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test @EnableFlags(FLAG_NOTIFICATION_SHADE_BLUR) fun transparencyEnabled_shadeBlurFlagOn_blurUnsupported_isDisabled() = testScope.runTest { kosmos.fakeWindowRootViewBlurRepository.isBlurSupported.value = false runCurrent() kosmos.runTest { fakeWindowRootViewBlurRepository.isBlurSupported.value = false assertThat(underTest.isTransparencyEnabled).isFalse() } @Test @EnableFlags(FLAG_NOTIFICATION_SHADE_BLUR) fun calculateTargetBlurRadius() = kosmos.runTest { // Only bouncer shown: no blur. fakeWindowRootViewBlurRepository.isBlurSupported.value = true assertThat( underTest.calculateTargetBlurRadius( transitionState = TransitionState.Idle( currentScene = Scenes.Lockscreen, currentOverlays = setOf(Overlays.Bouncer), ) ) ) .isEqualTo(0f) // Notifications shade and bouncer shown: apply blur. assertThat( underTest.calculateTargetBlurRadius( transitionState = TransitionState.Idle( currentScene = Scenes.Lockscreen, currentOverlays = setOf(Overlays.Bouncer, Overlays.NotificationsShade), ) ) ) .isEqualTo(blurConfig.maxBlurRadiusPx) // No bouncer shown: no blur. assertThat( underTest.calculateTargetBlurRadius( transitionState = TransitionState.Idle( currentScene = Scenes.Lockscreen, currentOverlays = setOf(Overlays.NotificationsShade), ) ) ) .isEqualTo(0) // Blur not supported: no blur. fakeWindowRootViewBlurRepository.isBlurSupported.value = false assertThat( underTest.calculateTargetBlurRadius( transitionState = TransitionState.Idle( currentScene = Scenes.Lockscreen, currentOverlays = setOf(Overlays.Bouncer, Overlays.NotificationsShade), ) ) ) .isEqualTo(0f) } @Test fun shadeModeChanged_single_switchesToShadeScene() = testScope.runTest { kosmos.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) Loading @@ -275,35 +318,34 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test fun shadeModeChanged_split_switchesToShadeScene() = testScope.runTest { kosmos.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) kosmos.enableDualShade() kosmos.shadeInteractor.expandNotificationsShade("test") enableDualShade() shadeInteractor.expandNotificationsShade("test") assertThat(currentScene).isEqualTo(Scenes.Lockscreen) assertThat(currentOverlays).contains(Overlays.NotificationsShade) kosmos.enableSplitShade() enableSplitShade() assertThat(currentScene).isEqualTo(Scenes.Shade) assertThat(currentOverlays).doesNotContain(Overlays.NotificationsShade) } private fun TestScope.lockDevice() { private fun Kosmos.lockDevice() { val currentScene by collectLastValue(sceneInteractor.currentScene) kosmos.powerInteractor.setAsleepForTest() runCurrent() powerInteractor.setAsleepForTest() assertThat(currentScene).isEqualTo(Scenes.Lockscreen) } private fun setUseDesktopStatusBar(enable: Boolean) { private fun Kosmos.setUseDesktopStatusBar(enable: Boolean) { overrideConfig(R.bool.config_useDesktopStatusBar, enable) kosmos.configurationController.onConfigurationChanged(Configuration()) configurationController.onConfigurationChanged(Configuration()) } private fun overrideConfig(configId: Int, value: Boolean) { kosmos.testableContext.orCreateTestableResources.addOverride(configId, value) private fun Kosmos.overrideConfig(configId: Int, value: Boolean) { testableContext.orCreateTestableResources.addOverride(configId, value) } private fun Kosmos.createTestInstance(): NotificationsShadeOverlayContentViewModel { Loading @@ -324,19 +366,15 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { mediaCarouselInteractor = mediaCarouselInteractor, windowRootViewBlurInteractor = windowRootViewBlurInteractor, desktopInteractor = desktopInteractor, blurConfig = blurConfig, mediaViewModelFactory = mediaViewModelFactory, ) } private suspend fun TestScope.unlockDevice() { private suspend fun Kosmos.unlockDevice() { val currentScene by collectLastValue(sceneInteractor.currentScene) kosmos.powerInteractor.setAwakeForTest() runCurrent() assertThat( kosmos.authenticationInteractor.authenticate( FakeAuthenticationRepository.DEFAULT_PIN ) ) powerInteractor.setAwakeForTest() assertThat(authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) assertThat(currentScene).isEqualTo(Scenes.Gone) Loading @@ -344,9 +382,9 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test fun onShadeBoundsChanged_forwardsToShadeOverlayInteractor() = testScope.runTest { kosmos.runTest { var shadeBounds: android.graphics.Rect? = null kosmos.shadeInteractor.addShadeOverlayBoundsListener { shadeBounds = it } shadeInteractor.addShadeOverlayBoundsListener { shadeBounds = it } assertThat(shadeBounds).isNull() val bounds = Rect(0f, 0f, 100f, 100f) Loading packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt +20 −1 Original line number Diff line number Diff line Loading @@ -21,9 +21,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Alignment import androidx.compose.ui.geometry.Rect import com.android.app.tracing.coroutines.launchTraced as launch import com.android.compose.animation.scene.content.state.TransitionState import com.android.systemui.Flags import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.desktop.domain.interactor.DesktopInteractor import com.android.systemui.keyguard.ui.transitions.BlurConfig import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor Loading @@ -31,6 +33,7 @@ import com.android.systemui.media.remedia.ui.compose.MediaUiBehavior import com.android.systemui.media.remedia.ui.viewmodel.MediaCarouselVisibility import com.android.systemui.media.remedia.ui.viewmodel.MediaViewModel import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.domain.interactor.ShadeModeInteractor Loading Loading @@ -65,13 +68,14 @@ constructor( @Main private val mainDispatcher: CoroutineDispatcher, val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory, val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory, private val desktopInteractor: DesktopInteractor, desktopInteractor: DesktopInteractor, val sceneInteractor: SceneInteractor, private val shadeInteractor: ShadeInteractor, private val shadeModeInteractor: ShadeModeInteractor, disableFlagsInteractor: DisableFlagsInteractor, private val mediaCarouselInteractor: MediaCarouselInteractor, val mediaViewModelFactory: MediaViewModel.Factory, private val blurConfig: BlurConfig, windowRootViewBlurInteractor: WindowRootViewBlurInteractor, ) : ExclusiveActivatable() { Loading Loading @@ -126,6 +130,21 @@ constructor( }, ) /** * Calculates the blur radius to apply to the overlay. * * @param transitionState The current transition state of the scene (from its `ContentScope`) * @return The blur radius to apply to the scene UI, in pixels. */ fun calculateTargetBlurRadius(transitionState: TransitionState): Float { return when { !isTransparencyEnabled -> 0f Overlays.NotificationsShade !in transitionState.currentOverlays -> 0f Overlays.Bouncer in transitionState.currentOverlays -> blurConfig.maxBlurRadiusPx else -> 0f } } val alignmentOnWideScreens = if (desktopInteractor.isNotificationShadeOnTopEnd) Alignment.TopEnd else Alignment.TopStart Loading packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt +2 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.shade.ui.viewmodel import com.android.systemui.desktop.domain.interactor.desktopInteractor import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testDispatcher Loading @@ -41,6 +42,7 @@ val Kosmos.notificationsShadeOverlayContentViewModel: shadeModeInteractor = shadeModeInteractor, disableFlagsInteractor = disableFlagsInteractor, mediaCarouselInteractor = mediaCarouselInteractor, blurConfig = blurConfig, windowRootViewBlurInteractor = windowRootViewBlurInteractor, desktopInteractor = desktopInteractor, mediaViewModelFactory = mediaViewModelFactory, Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt +14 −3 Original line number Diff line number Diff line Loading @@ -16,11 +16,17 @@ package com.android.systemui.notifications.ui.composable import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.blur import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey Loading @@ -29,7 +35,6 @@ import com.android.compose.animation.scene.UserActionResult import com.android.compose.lifecycle.DisposableEffectWithLifecycle import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.media.remedia.ui.compose.Media import com.android.systemui.media.remedia.ui.compose.MediaPresentationStyle Loading @@ -56,7 +61,6 @@ constructor( private val contentViewModelFactory: NotificationsShadeOverlayContentViewModel.Factory, private val shadeSession: SaveableSession, private val stackScrollView: Lazy<NotificationScrollView>, private val keyguardClockViewModel: KeyguardClockViewModel, private val jankMonitor: InteractionJankMonitor, ) : Overlay { override val key = Overlays.NotificationsShade Loading Loading @@ -92,11 +96,18 @@ constructor( val isFullWidth = isFullWidthShade() val targetBlurRadiusPx: Float by remember(layoutState) { derivedStateOf { viewModel.calculateTargetBlurRadius(layoutState.transitionState) } } val animatedBlurRadiusPx: Float by animateFloatAsState(targetValue = targetBlurRadiusPx, label = "NSOverlay-blurRadius") OverlayShade( panelElement = NotificationsShade.Elements.Panel, alignmentOnWideScreens = viewModel.alignmentOnWideScreens, enableTransparency = viewModel.isTransparencyEnabled, modifier = modifier, modifier = modifier.blur(with(LocalDensity.current) { animatedBlurRadiusPx.toDp() }), onScrimClicked = viewModel::onScrimClicked, onBackgroundPlaced = { bounds, _, _ -> viewModel.onShadeOverlayBoundsChanged(bounds) }, header = { Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt +115 −77 Original line number Diff line number Diff line Loading @@ -27,17 +27,20 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.geometry.Rect import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.content.state.TransitionState import com.android.systemui.Flags.FLAG_NOTIFICATION_SHADE_BLUR import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.domain.interactor.AuthenticationResult import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.desktop.domain.interactor.DesktopInteractor import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.backgroundScope import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher Loading Loading @@ -70,16 +73,11 @@ import com.android.systemui.testKosmos import com.android.systemui.window.data.repository.fakeWindowRootViewBlurRepository import com.android.systemui.window.domain.interactor.windowRootViewBlurInteractor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.update import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper Loading @@ -87,21 +85,19 @@ import org.junit.runner.RunWith class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val testScope = kosmos.testScope private val sceneInteractor by lazy { kosmos.sceneInteractor } private val underTest by lazy { kosmos.notificationsShadeOverlayContentViewModel } private val Kosmos.underTest by Fixture { notificationsShadeOverlayContentViewModel } @Before fun setUp() { kosmos.sceneContainerStartable.start() kosmos.enableDualShade() kosmos.runCurrent() fun setUp() = with(kosmos) { sceneContainerStartable.start() enableDualShade() underTest.activateIn(testScope) } @Test fun showHeader_desktopStatusBarDisabled_true() = testScope.runTest { kosmos.runTest { setUseDesktopStatusBar(false) assertThat(underTest.showHeader).isTrue() } Loading @@ -109,7 +105,7 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test @EnableFlags(StatusBarForDesktop.FLAG_NAME) fun showHeader_desktopStatusBarEnabled_statusBarForDesktopEnabled_false() = testScope.runTest { kosmos.runTest { setUseDesktopStatusBar(true) assertThat(underTest.showHeader).isFalse() } Loading @@ -117,7 +113,7 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test @DisableFlags(StatusBarForDesktop.FLAG_NAME) fun showHeader_desktopStatusBarEnabled_statusBarForDesktopDisabled_true() = testScope.runTest { kosmos.runTest { setUseDesktopStatusBar(true) assertThat(underTest.showHeader).isTrue() } Loading @@ -125,32 +121,29 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test @DisableFlags(StatusBarForDesktop.FLAG_NAME) fun alignmentOnWideScreens_statusBarForDesktopDisabled_topStart() = testScope.runTest { assertThat(kosmos.createTestInstance().alignmentOnWideScreens) .isEqualTo(Alignment.TopStart) kosmos.runTest { assertThat(createTestInstance().alignmentOnWideScreens).isEqualTo(Alignment.TopStart) } @Test @EnableFlags(StatusBarForDesktop.FLAG_NAME) fun alignmentOnWideScreens_configDisabled_statusBarForDesktopEnabled_topStart() = testScope.runTest { kosmos.runTest { overrideConfig(R.bool.config_notificationShadeOnTopEnd, false) assertThat(kosmos.createTestInstance().alignmentOnWideScreens) .isEqualTo(Alignment.TopStart) assertThat(createTestInstance().alignmentOnWideScreens).isEqualTo(Alignment.TopStart) } @Test @EnableFlags(StatusBarForDesktop.FLAG_NAME) fun alignmentOnWideScreens_configEnabled_statusBarForDesktopEnabled_topEnd() = testScope.runTest { kosmos.runTest { overrideConfig(R.bool.config_notificationShadeOnTopEnd, true) assertThat(kosmos.createTestInstance().alignmentOnWideScreens) .isEqualTo(Alignment.TopEnd) assertThat(createTestInstance().alignmentOnWideScreens).isEqualTo(Alignment.TopEnd) } @Test fun onScrimClicked_hidesShade() = testScope.runTest { kosmos.runTest { val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) sceneInteractor.showOverlay(Overlays.NotificationsShade, "test") assertThat(currentOverlays).contains(Overlays.NotificationsShade) Loading @@ -162,7 +155,7 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test fun deviceLocked_hidesShade() = testScope.runTest { kosmos.runTest { val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) unlockDevice() sceneInteractor.showOverlay(Overlays.NotificationsShade, "test") Loading @@ -175,9 +168,9 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test fun shadeNotTouchable_hidesShade() = testScope.runTest { kosmos.runTest { val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val isShadeTouchable by collectLastValue(kosmos.shadeInteractor.isShadeTouchable) val isShadeTouchable by collectLastValue(shadeInteractor.isShadeTouchable) assertThat(isShadeTouchable).isTrue() sceneInteractor.showOverlay(Overlays.NotificationsShade, "test") assertThat(currentOverlays).contains(Overlays.NotificationsShade) Loading @@ -189,40 +182,36 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test fun showMedia_activeMedia_true() = testScope.runTest { kosmos.mediaPipelineRepository.addCurrentUserMediaEntry(MediaData(active = true)) runCurrent() kosmos.runTest { mediaPipelineRepository.addCurrentUserMediaEntry(MediaData(active = true)) assertThat(underTest.showMedia).isTrue() } @Test fun showMedia_InactiveMedia_false() = testScope.runTest { kosmos.mediaPipelineRepository.addCurrentUserMediaEntry(MediaData(active = false)) runCurrent() kosmos.runTest { mediaPipelineRepository.addCurrentUserMediaEntry(MediaData(active = false)) assertThat(underTest.showMedia).isFalse() } @Test fun showMedia_noMedia_false() = testScope.runTest { kosmos.mediaPipelineRepository.addCurrentUserMediaEntry(MediaData(active = true)) kosmos.mediaPipelineRepository.clearCurrentUserMedia() runCurrent() kosmos.runTest { mediaPipelineRepository.addCurrentUserMediaEntry(MediaData(active = true)) mediaPipelineRepository.clearCurrentUserMedia() assertThat(underTest.showMedia).isFalse() } @Test fun showMedia_qsDisabled_false() = testScope.runTest { kosmos.mediaPipelineRepository.addCurrentUserMediaEntry(MediaData(active = true)) kosmos.fakeDisableFlagsRepository.disableFlags.update { kosmos.runTest { mediaPipelineRepository.addCurrentUserMediaEntry(MediaData(active = true)) fakeDisableFlagsRepository.disableFlags.update { it.copy(disable2 = DISABLE2_QUICK_SETTINGS) } runCurrent() assertThat(underTest.showMedia).isFalse() } Loading @@ -230,9 +219,8 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test @DisableFlags(FLAG_NOTIFICATION_SHADE_BLUR) fun transparencyEnabled_shadeBlurFlagOff_isDisabled() = testScope.runTest { kosmos.fakeWindowRootViewBlurRepository.isBlurSupported.value = true runCurrent() kosmos.runTest { fakeWindowRootViewBlurRepository.isBlurSupported.value = true assertThat(underTest.isTransparencyEnabled).isFalse() } Loading @@ -240,9 +228,8 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test @EnableFlags(FLAG_NOTIFICATION_SHADE_BLUR) fun transparencyEnabled_shadeBlurFlagOn_blurSupported_isEnabled() = testScope.runTest { kosmos.fakeWindowRootViewBlurRepository.isBlurSupported.value = true runCurrent() kosmos.runTest { fakeWindowRootViewBlurRepository.isBlurSupported.value = true assertThat(underTest.isTransparencyEnabled).isTrue() } Loading @@ -250,16 +237,72 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test @EnableFlags(FLAG_NOTIFICATION_SHADE_BLUR) fun transparencyEnabled_shadeBlurFlagOn_blurUnsupported_isDisabled() = testScope.runTest { kosmos.fakeWindowRootViewBlurRepository.isBlurSupported.value = false runCurrent() kosmos.runTest { fakeWindowRootViewBlurRepository.isBlurSupported.value = false assertThat(underTest.isTransparencyEnabled).isFalse() } @Test @EnableFlags(FLAG_NOTIFICATION_SHADE_BLUR) fun calculateTargetBlurRadius() = kosmos.runTest { // Only bouncer shown: no blur. fakeWindowRootViewBlurRepository.isBlurSupported.value = true assertThat( underTest.calculateTargetBlurRadius( transitionState = TransitionState.Idle( currentScene = Scenes.Lockscreen, currentOverlays = setOf(Overlays.Bouncer), ) ) ) .isEqualTo(0f) // Notifications shade and bouncer shown: apply blur. assertThat( underTest.calculateTargetBlurRadius( transitionState = TransitionState.Idle( currentScene = Scenes.Lockscreen, currentOverlays = setOf(Overlays.Bouncer, Overlays.NotificationsShade), ) ) ) .isEqualTo(blurConfig.maxBlurRadiusPx) // No bouncer shown: no blur. assertThat( underTest.calculateTargetBlurRadius( transitionState = TransitionState.Idle( currentScene = Scenes.Lockscreen, currentOverlays = setOf(Overlays.NotificationsShade), ) ) ) .isEqualTo(0) // Blur not supported: no blur. fakeWindowRootViewBlurRepository.isBlurSupported.value = false assertThat( underTest.calculateTargetBlurRadius( transitionState = TransitionState.Idle( currentScene = Scenes.Lockscreen, currentOverlays = setOf(Overlays.Bouncer, Overlays.NotificationsShade), ) ) ) .isEqualTo(0f) } @Test fun shadeModeChanged_single_switchesToShadeScene() = testScope.runTest { kosmos.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) Loading @@ -275,35 +318,34 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test fun shadeModeChanged_split_switchesToShadeScene() = testScope.runTest { kosmos.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) kosmos.enableDualShade() kosmos.shadeInteractor.expandNotificationsShade("test") enableDualShade() shadeInteractor.expandNotificationsShade("test") assertThat(currentScene).isEqualTo(Scenes.Lockscreen) assertThat(currentOverlays).contains(Overlays.NotificationsShade) kosmos.enableSplitShade() enableSplitShade() assertThat(currentScene).isEqualTo(Scenes.Shade) assertThat(currentOverlays).doesNotContain(Overlays.NotificationsShade) } private fun TestScope.lockDevice() { private fun Kosmos.lockDevice() { val currentScene by collectLastValue(sceneInteractor.currentScene) kosmos.powerInteractor.setAsleepForTest() runCurrent() powerInteractor.setAsleepForTest() assertThat(currentScene).isEqualTo(Scenes.Lockscreen) } private fun setUseDesktopStatusBar(enable: Boolean) { private fun Kosmos.setUseDesktopStatusBar(enable: Boolean) { overrideConfig(R.bool.config_useDesktopStatusBar, enable) kosmos.configurationController.onConfigurationChanged(Configuration()) configurationController.onConfigurationChanged(Configuration()) } private fun overrideConfig(configId: Int, value: Boolean) { kosmos.testableContext.orCreateTestableResources.addOverride(configId, value) private fun Kosmos.overrideConfig(configId: Int, value: Boolean) { testableContext.orCreateTestableResources.addOverride(configId, value) } private fun Kosmos.createTestInstance(): NotificationsShadeOverlayContentViewModel { Loading @@ -324,19 +366,15 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { mediaCarouselInteractor = mediaCarouselInteractor, windowRootViewBlurInteractor = windowRootViewBlurInteractor, desktopInteractor = desktopInteractor, blurConfig = blurConfig, mediaViewModelFactory = mediaViewModelFactory, ) } private suspend fun TestScope.unlockDevice() { private suspend fun Kosmos.unlockDevice() { val currentScene by collectLastValue(sceneInteractor.currentScene) kosmos.powerInteractor.setAwakeForTest() runCurrent() assertThat( kosmos.authenticationInteractor.authenticate( FakeAuthenticationRepository.DEFAULT_PIN ) ) powerInteractor.setAwakeForTest() assertThat(authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) assertThat(currentScene).isEqualTo(Scenes.Gone) Loading @@ -344,9 +382,9 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { @Test fun onShadeBoundsChanged_forwardsToShadeOverlayInteractor() = testScope.runTest { kosmos.runTest { var shadeBounds: android.graphics.Rect? = null kosmos.shadeInteractor.addShadeOverlayBoundsListener { shadeBounds = it } shadeInteractor.addShadeOverlayBoundsListener { shadeBounds = it } assertThat(shadeBounds).isNull() val bounds = Rect(0f, 0f, 100f, 100f) Loading
packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt +20 −1 Original line number Diff line number Diff line Loading @@ -21,9 +21,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Alignment import androidx.compose.ui.geometry.Rect import com.android.app.tracing.coroutines.launchTraced as launch import com.android.compose.animation.scene.content.state.TransitionState import com.android.systemui.Flags import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.desktop.domain.interactor.DesktopInteractor import com.android.systemui.keyguard.ui.transitions.BlurConfig import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor Loading @@ -31,6 +33,7 @@ import com.android.systemui.media.remedia.ui.compose.MediaUiBehavior import com.android.systemui.media.remedia.ui.viewmodel.MediaCarouselVisibility import com.android.systemui.media.remedia.ui.viewmodel.MediaViewModel import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.domain.interactor.ShadeModeInteractor Loading Loading @@ -65,13 +68,14 @@ constructor( @Main private val mainDispatcher: CoroutineDispatcher, val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory, val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory, private val desktopInteractor: DesktopInteractor, desktopInteractor: DesktopInteractor, val sceneInteractor: SceneInteractor, private val shadeInteractor: ShadeInteractor, private val shadeModeInteractor: ShadeModeInteractor, disableFlagsInteractor: DisableFlagsInteractor, private val mediaCarouselInteractor: MediaCarouselInteractor, val mediaViewModelFactory: MediaViewModel.Factory, private val blurConfig: BlurConfig, windowRootViewBlurInteractor: WindowRootViewBlurInteractor, ) : ExclusiveActivatable() { Loading Loading @@ -126,6 +130,21 @@ constructor( }, ) /** * Calculates the blur radius to apply to the overlay. * * @param transitionState The current transition state of the scene (from its `ContentScope`) * @return The blur radius to apply to the scene UI, in pixels. */ fun calculateTargetBlurRadius(transitionState: TransitionState): Float { return when { !isTransparencyEnabled -> 0f Overlays.NotificationsShade !in transitionState.currentOverlays -> 0f Overlays.Bouncer in transitionState.currentOverlays -> blurConfig.maxBlurRadiusPx else -> 0f } } val alignmentOnWideScreens = if (desktopInteractor.isNotificationShadeOnTopEnd) Alignment.TopEnd else Alignment.TopStart Loading
packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt +2 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.shade.ui.viewmodel import com.android.systemui.desktop.domain.interactor.desktopInteractor import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testDispatcher Loading @@ -41,6 +42,7 @@ val Kosmos.notificationsShadeOverlayContentViewModel: shadeModeInteractor = shadeModeInteractor, disableFlagsInteractor = disableFlagsInteractor, mediaCarouselInteractor = mediaCarouselInteractor, blurConfig = blurConfig, windowRootViewBlurInteractor = windowRootViewBlurInteractor, desktopInteractor = desktopInteractor, mediaViewModelFactory = mediaViewModelFactory, Loading