diff options
11 files changed, 244 insertions, 166 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt index ba85f9570d09..5806458da9b5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt @@ -20,11 +20,8 @@ import android.view.View import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView -import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ContentScope import com.android.internal.jank.Cuj import com.android.internal.jank.Cuj.CujType @@ -70,8 +67,7 @@ class LockscreenContent( rememberViewModel("LockscreenContent-scrimViewModel") { notificationScrimViewModelFactory.create() } - val isContentVisible: Boolean by viewModel.isContentVisible.collectAsStateWithLifecycle() - if (!isContentVisible) { + if (!viewModel.isContentVisible) { // If the content isn't supposed to be visible, show a large empty box as it's needed // for scene transition animations (can't just skip rendering everything or shared // elements won't have correct final/initial bounds from animating in and out of the @@ -80,15 +76,13 @@ class LockscreenContent( return } - val coroutineScope = rememberCoroutineScope() - val blueprintId by viewModel.blueprintId(coroutineScope).collectAsStateWithLifecycle() DisposableEffect(view) { clockInteractor.clockEventController.registerListeners(view) onDispose { clockInteractor.clockEventController.unregisterListeners() } } - val blueprint = blueprintByBlueprintId[blueprintId] ?: return + val blueprint = blueprintByBlueprintId[viewModel.blueprintId] ?: return with(blueprint) { Content(viewModel, modifier.sysuiResTag("keyguard_root_view")) NotificationLockscreenScrim(notificationLockscreenScrimViewModel) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt index d2fff06ad746..590a74ee2a0d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt @@ -23,7 +23,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer @@ -32,7 +31,6 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ContentScope import com.android.compose.modifiers.padding import com.android.systemui.compose.modifiers.sysuiResTag @@ -45,6 +43,8 @@ import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection import com.android.systemui.keyguard.ui.composable.section.StatusBarSection import com.android.systemui.keyguard.ui.composable.section.TopAreaSection import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel.NotificationsPlacement.BelowClock +import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel.NotificationsPlacement.BesideClock import com.android.systemui.res.R import java.util.Optional import javax.inject.Inject @@ -71,11 +71,8 @@ constructor( @Composable override fun ContentScope.Content(viewModel: LockscreenContentViewModel, modifier: Modifier) { val isUdfpsVisible = viewModel.isUdfpsVisible - val isShadeLayoutWide by viewModel.isShadeLayoutWide.collectAsStateWithLifecycle() - val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle() - val areNotificationsVisible by - viewModel.areNotificationsVisible().collectAsStateWithLifecycle(initialValue = false) - val isBypassEnabled by viewModel.isBypassEnabled.collectAsStateWithLifecycle() + val isBypassEnabled = viewModel.isBypassEnabled + val notificationsPlacement = viewModel.notificationsPlacement if (isBypassEnabled) { with(notificationSection) { HeadsUpNotifications() } @@ -92,7 +89,9 @@ constructor( modifier = Modifier.fillMaxWidth() .padding( - horizontal = { unfoldTranslations.start.roundToInt() } + horizontal = { + viewModel.unfoldTranslations.start.roundToInt() + } ) ) } @@ -101,28 +100,28 @@ constructor( with(topAreaSection) { DefaultClockLayout( smartSpacePaddingTop = viewModel::getSmartSpacePaddingTop, - isShadeLayoutWide = isShadeLayoutWide, modifier = Modifier.fillMaxWidth().graphicsLayer { - translationX = unfoldTranslations.start + translationX = viewModel.unfoldTranslations.start }, ) } - if (isShadeLayoutWide && !isBypassEnabled) { + if (notificationsPlacement is BesideClock && !isBypassEnabled) { with(notificationSection) { Box(modifier = Modifier.fillMaxHeight()) { AodPromotedNotificationArea( modifier = Modifier.fillMaxWidth(0.5f) - .align(alignment = Alignment.TopEnd) + .align(notificationsPlacement.alignment) ) Notifications( - areNotificationsVisible = areNotificationsVisible, + areNotificationsVisible = + viewModel.areNotificationsVisible, burnInParams = null, modifier = Modifier.fillMaxWidth(0.5f) .fillMaxHeight() - .align(alignment = Alignment.TopEnd) + .align(notificationsPlacement.alignment) .padding(top = 12.dp), ) } @@ -138,7 +137,7 @@ constructor( dimensionResource(R.dimen.below_clock_padding_start_icons) with(notificationSection) { - if (!isShadeLayoutWide && !isBypassEnabled) { + if (notificationsPlacement is BelowClock && !isBypassEnabled) { Box(modifier = Modifier.weight(weight = 1f)) { Column(Modifier.align(alignment = Alignment.TopStart)) { AodPromotedNotificationArea( @@ -150,13 +149,13 @@ constructor( ) } Notifications( - areNotificationsVisible = areNotificationsVisible, + areNotificationsVisible = viewModel.areNotificationsVisible, burnInParams = null, ) } } else { Column { - if (!isShadeLayoutWide) { + if (viewModel.notificationsPlacement is BelowClock) { AodPromotedNotificationArea( modifier = Modifier.padding(top = aodPromotedNotifTopPadding) @@ -204,13 +203,17 @@ constructor( isStart = true, applyPadding = true, modifier = - Modifier.graphicsLayer { translationX = unfoldTranslations.start }, + Modifier.graphicsLayer { + translationX = viewModel.unfoldTranslations.start + }, ) Shortcut( isStart = false, applyPadding = true, modifier = - Modifier.graphicsLayer { translationX = unfoldTranslations.end }, + Modifier.graphicsLayer { + translationX = viewModel.unfoldTranslations.end + }, ) } with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt index d8b3f742b447..0876631cf5c1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt @@ -41,16 +41,13 @@ constructor( ) { @Composable - fun ContentScope.KeyguardMediaCarousel( - isShadeLayoutWide: Boolean, - modifier: Modifier = Modifier, - ) { + fun ContentScope.KeyguardMediaCarousel(modifier: Modifier = Modifier) { val viewModel = rememberViewModel(traceName = "KeyguardMediaCarousel") { keyguardMediaViewModelFactory.create() } val horizontalPadding = - if (isShadeLayoutWide) { + if (viewModel.isShadeLayoutWide) { dimensionResource(id = R.dimen.notification_side_paddings) } else { dimensionResource(id = R.dimen.notification_side_paddings) + diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt index 6293fc26f96a..013424006668 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt @@ -60,7 +60,6 @@ constructor( @Composable fun ContentScope.DefaultClockLayout( smartSpacePaddingTop: (Resources) -> Int, - isShadeLayoutWide: Boolean, modifier: Modifier = Modifier, ) { val currentClockLayout by clockViewModel.currentClockLayout.collectAsStateWithLifecycle() @@ -128,7 +127,7 @@ constructor( ) } } - with(mediaCarouselSection) { KeyguardMediaCarousel(isShadeLayoutWide) } + with(mediaCarouselSection) { KeyguardMediaCarousel() } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 3a016ff7152a..63770803ff48 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -40,7 +40,6 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSec import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSliceViewSection import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection -import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import com.android.systemui.util.mockito.whenever import java.util.Optional import org.junit.Before @@ -66,7 +65,6 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { @Mock private lateinit var defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection @Mock private lateinit var defaultStatusBarViewSection: DefaultStatusBarSection @Mock private lateinit var defaultNSSLSection: DefaultNotificationStackScrollLayoutSection - @Mock private lateinit var splitShadeGuidelines: SplitShadeGuidelines @Mock private lateinit var aodPromotedNotificationSection: AodPromotedNotificationSection @Mock private lateinit var aodNotificationIconsSection: AodNotificationIconsSection @Mock private lateinit var aodBurnInSection: AodBurnInSection diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelTest.kt index 38829da69c28..583fd1e03002 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.lifecycle.activateIn import com.android.systemui.media.controls.data.repository.mediaFilterRepository import com.android.systemui.media.controls.shared.model.MediaData +import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -81,4 +82,20 @@ class KeyguardMediaViewModelTest : SysuiTestCase() { assertThat(underTest.isMediaVisible).isFalse() } + + @Test + fun isShadeLayoutWide_withConfigTrue_true() = + kosmos.runTest { + shadeRepository.setShadeLayoutWide(true) + + assertThat(underTest.isShadeLayoutWide).isTrue() + } + + @Test + fun isShadeLayoutWide_withConfigFalse_false() = + kosmos.runTest { + shadeRepository.setShadeLayoutWide(false) + + assertThat(underTest.isShadeLayoutWide).isFalse() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt index af025273458f..25c157208513 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.viewmodel import android.platform.test.flag.junit.FlagsParameterization +import androidx.compose.ui.Alignment import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.authController @@ -26,10 +27,13 @@ import com.android.systemui.flags.andSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository +import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor import com.android.systemui.keyguard.shared.model.ClockSize import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.transition.fakeKeyguardTransitionAnimationCallback import com.android.systemui.keyguard.shared.transition.keyguardTransitionAnimationCallbackDelegator +import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel.NotificationsPlacement.BelowClock +import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel.NotificationsPlacement.BesideClock import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runCurrent @@ -41,6 +45,9 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.domain.interactor.enableDualShade +import com.android.systemui.shade.domain.interactor.enableSingleShade +import com.android.systemui.shade.domain.interactor.enableSplitShade +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.testKosmos import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider import com.android.systemui.util.mockito.whenever @@ -99,136 +106,140 @@ class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCa } @Test - @DisableSceneContainer - fun clockSize_withLargeClock_true() = + fun notificationsPlacement_splitShade_topEnd() = kosmos.runTest { - val clockSize by collectLastValue(underTest.clockSize) - fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE) - assertThat(clockSize).isEqualTo(ClockSize.LARGE) + setupState(shadeMode = ShadeMode.Split, clockSize = ClockSize.SMALL) + + assertThat(underTest.notificationsPlacement) + .isEqualTo(BesideClock(alignment = Alignment.TopEnd)) } @Test - @DisableSceneContainer - fun clockSize_withSmallClock_false() = + fun notificationsPlacement_singleShade_below() = kosmos.runTest { - val clockSize by collectLastValue(underTest.clockSize) - fakeKeyguardClockRepository.setClockSize(ClockSize.SMALL) - assertThat(clockSize).isEqualTo(ClockSize.SMALL) + setupState(shadeMode = ShadeMode.Single, clockSize = ClockSize.SMALL) + + assertThat(underTest.notificationsPlacement).isEqualTo(BelowClock) } @Test - fun areNotificationsVisible_splitShadeTrue_true() = + fun notificationsPlacement_dualShadeSmallClock_below() = kosmos.runTest { - val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible()) - shadeRepository.setShadeLayoutWide(true) - fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE) + setupState( + shadeMode = ShadeMode.Dual, + clockSize = ClockSize.SMALL, + shadeLayoutWide = true, + ) - assertThat(areNotificationsVisible).isTrue() + assertThat(underTest.notificationsPlacement).isEqualTo(BelowClock) } @Test - fun areNotificationsVisible_dualShadeWideOnLockscreen_true() = + fun notificationsPlacement_dualShadeLargeClock_topStart() = kosmos.runTest { - val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible()) - kosmos.enableDualShade() - shadeRepository.setShadeLayoutWide(true) - fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE) + setupState( + shadeMode = ShadeMode.Dual, + clockSize = ClockSize.LARGE, + shadeLayoutWide = true, + ) - assertThat(areNotificationsVisible).isTrue() + assertThat(underTest.notificationsPlacement) + .isEqualTo(BesideClock(alignment = Alignment.TopStart)) } @Test - @DisableSceneContainer - fun areNotificationsVisible_withSmallClock_true() = + fun areNotificationsVisible_splitShadeTrue_true() = kosmos.runTest { - val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible()) - fakeKeyguardClockRepository.setClockSize(ClockSize.SMALL) - assertThat(areNotificationsVisible).isTrue() + setupState(shadeMode = ShadeMode.Split, clockSize = ClockSize.LARGE) + + assertThat(underTest.areNotificationsVisible).isTrue() } @Test - @DisableSceneContainer - fun areNotificationsVisible_withLargeClock_false() = + fun areNotificationsVisible_dualShadeWideOnLockscreen_true() = kosmos.runTest { - val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible()) - fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE) - assertThat(areNotificationsVisible).isFalse() + setupState( + shadeMode = ShadeMode.Dual, + clockSize = ClockSize.LARGE, + shadeLayoutWide = true, + ) + + assertThat(underTest.areNotificationsVisible).isTrue() } @Test - fun isShadeLayoutWide_withConfigTrue_true() = + @DisableSceneContainer + fun areNotificationsVisible_withSmallClock_true() = kosmos.runTest { - val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide) - shadeRepository.setShadeLayoutWide(true) + setupState(shadeMode = ShadeMode.Single, clockSize = ClockSize.SMALL) - assertThat(isShadeLayoutWide).isTrue() + assertThat(underTest.areNotificationsVisible).isTrue() } @Test - fun isShadeLayoutWide_withConfigFalse_false() = + @DisableSceneContainer + fun areNotificationsVisible_withLargeClock_false() = kosmos.runTest { - val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide) - shadeRepository.setShadeLayoutWide(false) + setupState(shadeMode = ShadeMode.Single, clockSize = ClockSize.LARGE) - assertThat(isShadeLayoutWide).isFalse() + assertThat(underTest.areNotificationsVisible).isFalse() } @Test fun unfoldTranslations() = kosmos.runTest { val maxTranslation = prepareConfiguration() - val translations by collectLastValue(underTest.unfoldTranslations) val unfoldProvider = fakeUnfoldTransitionProgressProvider unfoldProvider.onTransitionStarted() - assertThat(translations?.start).isEqualTo(0f) - assertThat(translations?.end).isEqualTo(-0f) + runCurrent() + assertThat(underTest.unfoldTranslations.start).isZero() + assertThat(underTest.unfoldTranslations.end).isZero() repeat(10) { repetition -> val transitionProgress = 0.1f * (repetition + 1) unfoldProvider.onTransitionProgress(transitionProgress) - assertThat(translations?.start).isEqualTo((1 - transitionProgress) * maxTranslation) - assertThat(translations?.end).isEqualTo(-(1 - transitionProgress) * maxTranslation) + runCurrent() + assertThat(underTest.unfoldTranslations.start) + .isEqualTo((1 - transitionProgress) * maxTranslation) + assertThat(underTest.unfoldTranslations.end) + .isEqualTo(-(1 - transitionProgress) * maxTranslation) } unfoldProvider.onTransitionFinishing() - assertThat(translations?.start).isEqualTo(0f) - assertThat(translations?.end).isEqualTo(-0f) + runCurrent() + assertThat(underTest.unfoldTranslations.start).isZero() + assertThat(underTest.unfoldTranslations.end).isZero() unfoldProvider.onTransitionFinished() - assertThat(translations?.start).isEqualTo(0f) - assertThat(translations?.end).isEqualTo(-0f) + runCurrent() + assertThat(underTest.unfoldTranslations.start).isZero() + assertThat(underTest.unfoldTranslations.end).isZero() } @Test fun isContentVisible_whenNotOccluded_visible() = kosmos.runTest { - val isContentVisible by collectLastValue(underTest.isContentVisible) - keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, null) runCurrent() - assertThat(isContentVisible).isTrue() + assertThat(underTest.isContentVisible).isTrue() } @Test fun isContentVisible_whenOccluded_notVisible() = kosmos.runTest { - val isContentVisible by collectLastValue(underTest.isContentVisible) - keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null) fakeKeyguardTransitionRepository.transitionTo( KeyguardState.LOCKSCREEN, KeyguardState.OCCLUDED, ) runCurrent() - assertThat(isContentVisible).isFalse() + assertThat(underTest.isContentVisible).isFalse() } @Test fun isContentVisible_whenOccluded_notVisible_evenIfShadeShown() = kosmos.runTest { - val isContentVisible by collectLastValue(underTest.isContentVisible) - keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null) fakeKeyguardTransitionRepository.transitionTo( KeyguardState.LOCKSCREEN, @@ -238,7 +249,7 @@ class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCa sceneInteractor.snapToScene(Scenes.Shade, "") runCurrent() - assertThat(isContentVisible).isFalse() + assertThat(underTest.isContentVisible).isFalse() } @Test @@ -260,17 +271,16 @@ class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCa @Test fun isContentVisible_whenOccluded_notVisibleInOccluded_visibleInAod() = kosmos.runTest { - val isContentVisible by collectLastValue(underTest.isContentVisible) keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null) fakeKeyguardTransitionRepository.transitionTo( - KeyguardState.LOCKSCREEN, - KeyguardState.OCCLUDED, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, ) runCurrent() sceneInteractor.snapToScene(Scenes.Shade, "") runCurrent() - assertThat(isContentVisible).isFalse() + assertThat(underTest.isContentVisible).isFalse() fakeKeyguardTransitionRepository.transitionTo(KeyguardState.OCCLUDED, KeyguardState.AOD) runCurrent() @@ -278,9 +288,30 @@ class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCa sceneInteractor.snapToScene(Scenes.Lockscreen, "") runCurrent() - assertThat(isContentVisible).isTrue() + assertThat(underTest.isContentVisible).isTrue() } + private fun Kosmos.setupState( + shadeMode: ShadeMode, + clockSize: ClockSize, + shadeLayoutWide: Boolean? = null, + ) { + val isShadeLayoutWide by collectLastValue(kosmos.shadeRepository.isShadeLayoutWide) + val collectedClockSize by collectLastValue(kosmos.keyguardClockInteractor.clockSize) + when (shadeMode) { + ShadeMode.Dual -> kosmos.enableDualShade(wideLayout = shadeLayoutWide) + ShadeMode.Single -> kosmos.enableSingleShade() + ShadeMode.Split -> kosmos.enableSplitShade() + } + fakeKeyguardClockRepository.setShouldForceSmallClock(clockSize == ClockSize.SMALL) + fakeKeyguardClockRepository.setClockSize(clockSize) + runCurrent() + if (shadeLayoutWide != null) { + assertThat(isShadeLayoutWide).isEqualTo(shadeLayoutWide) + } + assertThat(collectedClockSize).isEqualTo(clockSize) + } + private fun prepareConfiguration(): Int { val configuration = context.resources.configuration configuration.setLayoutDirection(Locale.US) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt index ba03c48c65e9..e70d696a207f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt @@ -21,6 +21,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor +import com.android.systemui.shade.domain.interactor.ShadeModeInteractor import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -31,6 +32,7 @@ class KeyguardMediaViewModel constructor( mediaCarouselInteractor: MediaCarouselInteractor, keyguardInteractor: KeyguardInteractor, + shadeModeInteractor: ShadeModeInteractor, ) : ExclusiveActivatable() { private val hydrator = Hydrator("KeyguardMediaViewModel.hydrator") @@ -54,6 +56,12 @@ constructor( mediaCarouselInteractor.hasActiveMediaOrRecommendation.value, ) + val isShadeLayoutWide: Boolean by + hydrator.hydratedStateOf( + traceName = "isShadeLayoutWide", + source = shadeModeInteractor.isShadeLayoutWide, + ) + override suspend fun onActivated(): Nothing { hydrator.activate() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt index 3e3a89a55f66..ecebaee62862 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt @@ -17,8 +17,9 @@ package com.android.systemui.keyguard.ui.viewmodel import android.content.res.Resources +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import com.android.app.tracing.coroutines.launchTraced as launch -import com.android.internal.annotations.VisibleForTesting import com.android.systemui.biometrics.AuthController import com.android.systemui.customization.R as customR import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor @@ -30,86 +31,121 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.transition.KeyguardTransitionAnimationCallback import com.android.systemui.keyguard.shared.transition.KeyguardTransitionAnimationCallbackDelegator import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.lifecycle.Hydrator import com.android.systemui.res.R -import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.domain.interactor.ShadeModeInteractor +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn class LockscreenContentViewModel @AssistedInject constructor( - clockInteractor: KeyguardClockInteractor, - private val interactor: KeyguardBlueprintInteractor, + private val clockInteractor: KeyguardClockInteractor, + interactor: KeyguardBlueprintInteractor, private val authController: AuthController, val touchHandling: KeyguardTouchHandlingViewModel, - private val shadeInteractor: ShadeInteractor, - private val unfoldTransitionInteractor: UnfoldTransitionInteractor, - private val deviceEntryInteractor: DeviceEntryInteractor, - private val transitionInteractor: KeyguardTransitionInteractor, + shadeModeInteractor: ShadeModeInteractor, + unfoldTransitionInteractor: UnfoldTransitionInteractor, + deviceEntryInteractor: DeviceEntryInteractor, + transitionInteractor: KeyguardTransitionInteractor, private val keyguardTransitionAnimationCallbackDelegator: KeyguardTransitionAnimationCallbackDelegator, @Assisted private val keyguardTransitionAnimationCallback: KeyguardTransitionAnimationCallback, ) : ExclusiveActivatable() { - @VisibleForTesting val clockSize = clockInteractor.clockSize + + private val hydrator = Hydrator("LockscreenContentViewModel.hydrator") val isUdfpsVisible: Boolean get() = authController.isUdfpsSupported - val isShadeLayoutWide: StateFlow<Boolean> = shadeInteractor.isShadeLayoutWide + /** Where to place the notifications stack on the lockscreen. */ + val notificationsPlacement: NotificationsPlacement by + hydrator.hydratedStateOf( + traceName = "notificationsPlacement", + initialValue = NotificationsPlacement.BelowClock, + source = + combine(shadeModeInteractor.shadeMode, clockInteractor.clockSize) { + shadeMode, + clockSize -> + if (shadeMode is ShadeMode.Split) { + NotificationsPlacement.BesideClock(alignment = Alignment.TopEnd) + } else if (clockSize == ClockSize.SMALL) { + NotificationsPlacement.BelowClock + } else { + NotificationsPlacement.BesideClock(alignment = Alignment.TopStart) + } + }, + ) - private val _unfoldTranslations = MutableStateFlow(UnfoldTranslations()) /** Amount of horizontal translation that should be applied to elements in the scene. */ - val unfoldTranslations: StateFlow<UnfoldTranslations> = _unfoldTranslations.asStateFlow() + val unfoldTranslations: UnfoldTranslations by + hydrator.hydratedStateOf( + traceName = "unfoldTranslations", + initialValue = UnfoldTranslations(), + source = + combine( + unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = true), + unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = false), + ::UnfoldTranslations, + ), + ) - private val _isContentVisible = MutableStateFlow(true) /** Whether the content of the scene UI should be shown. */ - val isContentVisible: StateFlow<Boolean> = _isContentVisible.asStateFlow() + val isContentVisible: Boolean by + hydrator.hydratedStateOf( + traceName = "isContentVisible", + initialValue = true, + // Content is visible unless we're OCCLUDED. Currently, we don't have nice animations + // into and out of OCCLUDED, so the lockscreen/AOD content is hidden immediately upon + // entering/exiting OCCLUDED. + source = transitionInteractor.transitionValue(KeyguardState.OCCLUDED).map { it == 0f }, + ) + + /** Indicates whether lockscreen notifications should be rendered. */ + val areNotificationsVisible: Boolean by + hydrator.hydratedStateOf( + traceName = "areNotificationsVisible", + initialValue = false, + // Content is visible unless we're OCCLUDED. Currently, we don't have nice animations + // into and out of OCCLUDED, so the lockscreen/AOD content is hidden immediately upon + // entering/exiting OCCLUDED. + source = + combine(clockInteractor.clockSize, shadeModeInteractor.isShadeLayoutWide) { + clockSize, + isShadeLayoutWide -> + clockSize == ClockSize.SMALL || isShadeLayoutWide + }, + ) /** @see DeviceEntryInteractor.isBypassEnabled */ - val isBypassEnabled: StateFlow<Boolean> - get() = deviceEntryInteractor.isBypassEnabled + val isBypassEnabled: Boolean by + hydrator.hydratedStateOf( + traceName = "isBypassEnabled", + source = deviceEntryInteractor.isBypassEnabled, + ) + + val blueprintId: String by + hydrator.hydratedStateOf( + traceName = "blueprintId", + initialValue = interactor.getCurrentBlueprint().id, + source = interactor.blueprint.map { it.id }.distinctUntilChanged(), + ) override suspend fun onActivated(): Nothing { coroutineScope { try { + launch { hydrator.activate() } + keyguardTransitionAnimationCallbackDelegator.delegate = keyguardTransitionAnimationCallback - launch { - combine( - unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = true), - unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = false), - ) { start, end -> - UnfoldTranslations(start = start, end = end) - } - .collect { _unfoldTranslations.value = it } - } - - launch { - transitionInteractor - .transitionValue(KeyguardState.OCCLUDED) - .map { it > 0f } - .collect { fullyOrPartiallyOccluded -> - // Content is visible unless we're OCCLUDED. Currently, we don't have - // nice - // animations into and out of OCCLUDED, so the lockscreen/AOD content is - // hidden immediately upon entering/exiting OCCLUDED. - _isContentVisible.value = !fullyOrPartiallyOccluded - } - } awaitCancellation() } finally { @@ -118,16 +154,8 @@ constructor( } } - /** Returns a flow that indicates whether lockscreen notifications should be rendered. */ - fun areNotificationsVisible(): Flow<Boolean> { - return combine(clockSize, shadeInteractor.isShadeLayoutWide) { clockSize, isShadeLayoutWide - -> - clockSize == ClockSize.SMALL || isShadeLayoutWide - } - } - fun getSmartSpacePaddingTop(resources: Resources): Int { - return if (clockSize.value == ClockSize.LARGE) { + return if (clockInteractor.clockSize.value == ClockSize.LARGE) { resources.getDimensionPixelSize(customR.dimen.keyguard_smartspace_top_offset) + resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) } else { @@ -135,17 +163,6 @@ constructor( } } - fun blueprintId(scope: CoroutineScope): StateFlow<String> { - return interactor.blueprint - .map { it.id } - .distinctUntilChanged() - .stateIn( - scope = scope, - started = SharingStarted.WhileSubscribed(), - initialValue = interactor.getCurrentBlueprint().id, - ) - } - data class UnfoldTranslations( /** @@ -162,6 +179,15 @@ constructor( val end: Float = 0f, ) + /** Where to place the notifications stack on the lockscreen. */ + sealed interface NotificationsPlacement { + /** Show notifications below the lockscreen clock. */ + data object BelowClock : NotificationsPlacement + + /** Show notifications side-by-side with the clock. */ + data class BesideClock(val alignment: Alignment) : NotificationsPlacement + } + @AssistedFactory interface Factory { fun create( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelFactoryKosmos.kt index 16d3fdc26613..345d69aa8df0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelFactoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelFactoryKosmos.kt @@ -19,12 +19,17 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor +import com.android.systemui.shade.domain.interactor.shadeModeInteractor val Kosmos.keyguardMediaViewModelFactory by Kosmos.Fixture { object : KeyguardMediaViewModel.Factory { override fun create(): KeyguardMediaViewModel { - return KeyguardMediaViewModel(mediaCarouselInteractor, keyguardInteractor) + return KeyguardMediaViewModel( + mediaCarouselInteractor = mediaCarouselInteractor, + keyguardInteractor = keyguardInteractor, + shadeModeInteractor = shadeModeInteractor, + ) } } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt index dd13b8b143ae..b751e213152e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt @@ -25,7 +25,7 @@ import com.android.systemui.keyguard.shared.transition.KeyguardTransitionAnimati import com.android.systemui.keyguard.shared.transition.keyguardTransitionAnimationCallbackDelegator import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.shade.domain.interactor.shadeModeInteractor import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor val Kosmos.lockscreenContentViewModelFactory by Fixture { @@ -38,7 +38,7 @@ val Kosmos.lockscreenContentViewModelFactory by Fixture { interactor = keyguardBlueprintInteractor, authController = authController, touchHandling = keyguardTouchHandlingViewModel, - shadeInteractor = shadeInteractor, + shadeModeInteractor = shadeModeInteractor, unfoldTransitionInteractor = unfoldTransitionInteractor, deviceEntryInteractor = deviceEntryInteractor, transitionInteractor = keyguardTransitionInteractor, |