summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt10
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt41
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt155
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt156
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelFactoryKosmos.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt4
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,