diff options
| author | 2024-01-03 01:10:13 +0000 | |
|---|---|---|
| committer | 2024-01-03 01:10:13 +0000 | |
| commit | 867d604f41ce5852a1459310029ceda849c7f89b (patch) | |
| tree | dae6959d968126f8527266235bda0b9d56334114 | |
| parent | 37ad976c77c471f3b5ac404913466147f6f7c8da (diff) | |
| parent | 0e2ae4ee41233044c09d0bea33627d5faadf3afb (diff) | |
Merge changes I10c4cbc5,I1b4affba into main
* changes:
[flexiglass] Adds horizontal spacing to status bar section.
[flexiglass] Adds burn-in to smartspace section elements.
32 files changed, 1288 insertions, 694 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt index 976161b3beb7..8119d2a119ca 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.core.view.isVisible import com.android.compose.animation.scene.SceneScope import com.android.systemui.keyguard.qualifiers.KeyguardRootView +import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel import com.android.systemui.notifications.ui.composable.NotificationStack import com.android.systemui.res.R @@ -47,8 +48,9 @@ import javax.inject.Inject class ViewBasedLockscreenContent @Inject constructor( - private val viewModel: LockscreenSceneViewModel, + private val lockscreenSceneViewModel: LockscreenSceneViewModel, @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View, + private val keyguardRootViewModel: KeyguardRootViewModel, ) { @Composable fun SceneScope.Content( @@ -59,7 +61,7 @@ constructor( } LockscreenLongPress( - viewModel = viewModel.longPress, + viewModel = lockscreenSceneViewModel.longPress, modifier = modifier, ) { onSettingsMenuPlaced -> AndroidView( @@ -74,7 +76,7 @@ constructor( ) val notificationStackPosition by - viewModel.keyguardRoot.notificationBounds.collectAsState() + keyguardRootViewModel.notificationBounds.collectAsState() Layout( modifier = @@ -92,7 +94,7 @@ constructor( }, content = { NotificationStack( - viewModel = viewModel.notifications, + viewModel = lockscreenSceneViewModel.notifications, isScrimVisible = false, ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt new file mode 100644 index 000000000000..c4184905f28d --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.composable.blueprint + +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.displayCutout +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.union +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalDensity +import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor +import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters +import com.android.systemui.plugins.clocks.ClockController +import kotlin.math.min +import kotlin.math.roundToInt + +/** Produces a [BurnInState] that can be used to query the `LockscreenBurnInViewModel` flows. */ +@Composable +fun rememberBurnIn( + clockInteractor: KeyguardClockInteractor, +): BurnInState { + val clock by clockInteractor.currentClock.collectAsState() + + val (smartspaceTop, onSmartspaceTopChanged) = remember { mutableStateOf<Float?>(null) } + val (smallClockTop, onSmallClockTopChanged) = remember { mutableStateOf<Float?>(null) } + + val topmostTop = + when { + smartspaceTop != null && smallClockTop != null -> min(smartspaceTop, smallClockTop) + smartspaceTop != null -> smartspaceTop + smallClockTop != null -> smallClockTop + else -> 0f + }.roundToInt() + + val params = rememberBurnInParameters(clock, topmostTop) + + return remember(params, onSmartspaceTopChanged, onSmallClockTopChanged) { + BurnInState( + parameters = params, + onSmartspaceTopChanged = onSmartspaceTopChanged, + onSmallClockTopChanged = onSmallClockTopChanged, + ) + } +} + +@Composable +private fun rememberBurnInParameters( + clock: ClockController?, + topmostTop: Int, +): BurnInParameters { + val density = LocalDensity.current + val topInset = WindowInsets.systemBars.union(WindowInsets.displayCutout).getTop(density) + + return remember(clock, topInset, topmostTop) { + BurnInParameters( + clockControllerProvider = { clock }, + topInset = topInset, + statusViewTop = topmostTop, + ) + } +} + +data class BurnInState( + /** Parameters for use with the `LockscreenBurnInViewModel. */ + val parameters: BurnInParameters, + /** + * Callback to invoke when the top coordinate of the smartspace element is updated, pass `null` + * when the element is not shown. + */ + val onSmartspaceTopChanged: (Float?) -> Unit, + /** + * Callback to invoke when the top coordinate of the small clock element is updated, pass `null` + * when the element is not shown. + */ + val onSmallClockTopChanged: (Float?) -> Unit, +) 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 d9d98cbd2da6..7385a251200e 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 @@ -24,6 +24,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout import androidx.compose.ui.unit.IntRect import com.android.compose.animation.scene.SceneScope +import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.LockscreenLongPress import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection @@ -55,6 +56,7 @@ constructor( private val ambientIndicationSection: AmbientIndicationSection, private val bottomAreaSection: BottomAreaSection, private val settingsMenuSection: SettingsMenuSection, + private val clockInteractor: KeyguardClockInteractor, ) : LockscreenSceneBlueprint { override val id: String = "default" @@ -62,6 +64,7 @@ constructor( @Composable override fun SceneScope.Content(modifier: Modifier) { val isUdfpsVisible = viewModel.isUdfpsVisible + val burnIn = rememberBurnIn(clockInteractor) LockscreenLongPress( viewModel = viewModel.longPress, @@ -74,8 +77,19 @@ constructor( modifier = Modifier.fillMaxWidth(), ) { with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) } - with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) } - with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) } + with(clockSection) { + SmallClock( + onTopChanged = burnIn.onSmallClockTopChanged, + modifier = Modifier.fillMaxWidth(), + ) + } + with(smartSpaceSection) { + SmartSpace( + burnInParams = burnIn.parameters, + onTopChanged = burnIn.onSmartspaceTopChanged, + modifier = Modifier.fillMaxWidth(), + ) + } with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) } with(notificationSection) { Notifications(modifier = Modifier.fillMaxWidth().weight(1f)) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt index 4704f5c3d1eb..acd47797baca 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout import androidx.compose.ui.unit.IntRect import com.android.compose.animation.scene.SceneScope +import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.LockscreenLongPress import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection @@ -55,6 +56,7 @@ constructor( private val ambientIndicationSection: AmbientIndicationSection, private val bottomAreaSection: BottomAreaSection, private val settingsMenuSection: SettingsMenuSection, + private val clockInteractor: KeyguardClockInteractor, ) : LockscreenSceneBlueprint { override val id: String = "shortcuts-besides-udfps" @@ -62,6 +64,7 @@ constructor( @Composable override fun SceneScope.Content(modifier: Modifier) { val isUdfpsVisible = viewModel.isUdfpsVisible + val burnIn = rememberBurnIn(clockInteractor) LockscreenLongPress( viewModel = viewModel.longPress, @@ -74,8 +77,19 @@ constructor( modifier = Modifier.fillMaxWidth(), ) { with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) } - with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) } - with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) } + with(clockSection) { + SmallClock( + onTopChanged = burnIn.onSmallClockTopChanged, + modifier = Modifier.fillMaxWidth(), + ) + } + with(smartSpaceSection) { + SmartSpace( + burnInParams = burnIn.parameters, + onTopChanged = burnIn.onSmartspaceTopChanged, + modifier = Modifier.fillMaxWidth(), + ) + } with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) } with(notificationSection) { Notifications(modifier = Modifier.fillMaxWidth().weight(1f)) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt new file mode 100644 index 000000000000..f9dd04b66b1f --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.composable.modifier + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.boundsInWindow +import androidx.compose.ui.layout.onPlaced +import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel +import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters +import com.android.systemui.keyguard.ui.viewmodel.BurnInScaleViewModel + +/** + * Modifies the composable to account for anti-burn in translation, alpha, and scaling. + * + * Please override [isClock] as `true` if the composable is an element that's part of a clock. + */ +@Composable +fun Modifier.burnInAware( + viewModel: AodBurnInViewModel, + params: BurnInParameters, + isClock: Boolean = false, +): Modifier { + val translationX by viewModel.translationX(params).collectAsState(initial = 0f) + val translationY by viewModel.translationY(params).collectAsState(initial = 0f) + val alpha by viewModel.alpha.collectAsState(initial = 1f) + val scaleViewModel by viewModel.scale(params).collectAsState(initial = BurnInScaleViewModel()) + + return this.graphicsLayer { + val scale = + when { + scaleViewModel.scaleClockOnly && isClock -> scaleViewModel.scale + !scaleViewModel.scaleClockOnly -> scaleViewModel.scale + else -> 1f + } + + this.translationX = translationX + this.translationY = translationY + this.alpha = alpha + this.scaleX = scale + this.scaleY = scale + } +} + +/** Reports the "top" coordinate of the modified composable to the given [consumer]. */ +@Composable +fun Modifier.onTopPlacementChanged( + consumer: (Float) -> Unit, +): Modifier { + return onPlaced { coordinates -> consumer(coordinates.boundsInWindow().top) } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt index db20f65ee78d..4f3498e8dff5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt @@ -35,10 +35,10 @@ import com.android.systemui.animation.view.LaunchableImageView import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea +import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel -import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.statusbar.KeyguardIndicationController @@ -55,7 +55,7 @@ constructor( private val vibratorHelper: VibratorHelper, private val indicationController: KeyguardIndicationController, private val indicationAreaViewModel: KeyguardIndicationAreaViewModel, - private val keyguardRootViewModel: KeyguardRootViewModel, + private val alphaViewModel: AodAlphaViewModel, ) { /** * Renders a single lockscreen shortcut. @@ -101,7 +101,7 @@ constructor( ) { IndicationArea( indicationAreaViewModel = indicationAreaViewModel, - keyguardRootViewModel = keyguardRootViewModel, + alphaViewModel = alphaViewModel, indicationController = indicationController, ) } @@ -179,7 +179,7 @@ constructor( @Composable private fun IndicationArea( indicationAreaViewModel: KeyguardIndicationAreaViewModel, - keyguardRootViewModel: KeyguardRootViewModel, + alphaViewModel: AodAlphaViewModel, indicationController: KeyguardIndicationController, modifier: Modifier = Modifier, ) { @@ -192,7 +192,7 @@ constructor( KeyguardIndicationAreaBinder.bind( view = view, viewModel = indicationAreaViewModel, - keyguardRootViewModel = keyguardRootViewModel, + aodAlphaViewModel = alphaViewModel, indicationController = indicationController, ) ) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt index eaf8063b6f15..0b49922a8412 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope +import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import javax.inject.Inject @@ -35,8 +36,12 @@ constructor( private val viewModel: KeyguardClockViewModel, ) { @Composable - fun SceneScope.SmallClock(modifier: Modifier = Modifier) { + fun SceneScope.SmallClock( + onTopChanged: (top: Float?) -> Unit, + modifier: Modifier = Modifier, + ) { if (viewModel.useLargeClock) { + onTopChanged(null) return } @@ -45,7 +50,10 @@ constructor( modifier = modifier, ) { Box( - modifier = Modifier.fillMaxWidth().background(Color.Magenta), + modifier = + Modifier.fillMaxWidth() + .background(Color.Magenta) + .onTopPlacementChanged(onTopChanged) ) { Text( text = "TODO(b/316211368): Small clock", diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt index 3c49cbcc1f7a..9b718444b75c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt @@ -36,6 +36,10 @@ import androidx.compose.ui.viewinterop.AndroidView import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope import com.android.systemui.keyguard.KeyguardUnlockAnimationController +import com.android.systemui.keyguard.ui.composable.modifier.burnInAware +import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged +import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel +import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController @@ -47,11 +51,16 @@ constructor( private val lockscreenSmartspaceController: LockscreenSmartspaceController, private val keyguardUnlockAnimationController: KeyguardUnlockAnimationController, private val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel, + private val aodBurnInViewModel: AodBurnInViewModel, ) { @Composable - fun SceneScope.SmartSpace(modifier: Modifier = Modifier) { + fun SceneScope.SmartSpace( + burnInParams: BurnInParameters, + onTopChanged: (top: Float?) -> Unit, + modifier: Modifier = Modifier, + ) { Column( - modifier = modifier.element(SmartSpaceElementKey), + modifier = modifier.element(SmartSpaceElementKey).onTopPlacementChanged(onTopChanged), ) { if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) { return @@ -71,9 +80,21 @@ constructor( start = paddingBelowClockStart, ), ) { - Date() + Date( + modifier = + Modifier.burnInAware( + viewModel = aodBurnInViewModel, + params = burnInParams, + ), + ) Spacer(modifier = Modifier.width(4.dp)) - Weather() + Weather( + modifier = + Modifier.burnInAware( + viewModel = aodBurnInViewModel, + params = burnInParams, + ), + ) } } @@ -84,6 +105,10 @@ constructor( start = paddingBelowClockStart, end = paddingBelowClockEnd, ) + .burnInAware( + viewModel = aodBurnInViewModel, + params = burnInParams, + ), ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt index 6811eb4cea5c..5727e3436126 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt @@ -21,9 +21,11 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope @@ -78,7 +80,7 @@ constructor( view }, modifier = - Modifier.fillMaxWidth().height { + Modifier.fillMaxWidth().padding(horizontal = 16.dp).height { Utils.getStatusBarHeaderHeightKeyguard(context) }, ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt new file mode 100644 index 000000000000..83782e214780 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class AodAlphaViewModelTest : SysuiTestCase() { + + @Mock + private lateinit var occludedToLockscreenTransitionViewModel: + OccludedToLockscreenTransitionViewModel + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val keyguardRepository = kosmos.fakeKeyguardRepository + private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val occludedToLockscreenAlpha = MutableStateFlow(0f) + + private lateinit var underTest: AodAlphaViewModel + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(occludedToLockscreenTransitionViewModel.lockscreenAlpha) + .thenReturn(occludedToLockscreenAlpha) + kosmos.occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel + + underTest = kosmos.aodAlphaViewModel + } + + @Test + fun alpha() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.OFF, + to = KeyguardState.LOCKSCREEN, + testScope = testScope, + ) + + keyguardRepository.setKeyguardAlpha(0.1f) + assertThat(alpha).isEqualTo(0.1f) + keyguardRepository.setKeyguardAlpha(0.5f) + assertThat(alpha).isEqualTo(0.5f) + keyguardRepository.setKeyguardAlpha(0.2f) + assertThat(alpha).isEqualTo(0.2f) + keyguardRepository.setKeyguardAlpha(0f) + assertThat(alpha).isEqualTo(0f) + occludedToLockscreenAlpha.value = 0.8f + assertThat(alpha).isEqualTo(0.8f) + } + + @Test + fun alpha_whenGone_equalsZero() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + testScope = testScope, + ) + + keyguardRepository.setKeyguardAlpha(0.1f) + assertThat(alpha).isEqualTo(0f) + keyguardRepository.setKeyguardAlpha(0.5f) + assertThat(alpha).isEqualTo(0f) + keyguardRepository.setKeyguardAlpha(1f) + assertThat(alpha).isEqualTo(0f) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt new file mode 100644 index 000000000000..0543bc257440 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.BurnInInteractor +import com.android.systemui.keyguard.domain.interactor.burnInInteractor +import com.android.systemui.keyguard.shared.model.BurnInModel +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.kosmos.testScope +import com.android.systemui.plugins.clocks.ClockController +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Answers +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class AodBurnInViewModelTest : SysuiTestCase() { + + @Mock private lateinit var burnInInteractor: BurnInInteractor + @Mock private lateinit var goneToAodTransitionViewModel: GoneToAodTransitionViewModel + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private lateinit var underTest: AodBurnInViewModel + + private var burnInParameters = + BurnInParameters( + clockControllerProvider = { clockController }, + ) + private val burnInFlow = MutableStateFlow(BurnInModel()) + private val enterFromTopAnimationAlpha = MutableStateFlow(0f) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow) + kosmos.burnInInteractor = burnInInteractor + whenever(goneToAodTransitionViewModel.enterFromTopAnimationAlpha) + .thenReturn(enterFromTopAnimationAlpha) + whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt())) + .thenReturn(emptyFlow()) + kosmos.goneToAodTransitionViewModel = goneToAodTransitionViewModel + + underTest = kosmos.aodBurnInViewModel + } + + @Test + fun translationY_initializedToZero() = + testScope.runTest { + val translationY by collectLastValue(underTest.translationY(burnInParameters)) + assertThat(translationY).isEqualTo(0) + } + + @Test + fun translationAndScale_whenNotDozing() = + testScope.runTest { + val translationX by collectLastValue(underTest.translationX(burnInParameters)) + val translationY by collectLastValue(underTest.translationY(burnInParameters)) + val scale by collectLastValue(underTest.scale(burnInParameters)) + + // Set to not dozing (on lockscreen) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = 1f, + transitionState = TransitionState.FINISHED + ), + validateStep = false, + ) + + // Trigger a change to the burn-in model + burnInFlow.value = + BurnInModel( + translationX = 20, + translationY = 30, + scale = 0.5f, + ) + + assertThat(translationX).isEqualTo(0) + assertThat(translationY).isEqualTo(0) + assertThat(scale) + .isEqualTo( + BurnInScaleViewModel( + scale = 1f, + scaleClockOnly = true, + ) + ) + } + + @Test + fun translationAndScale_whenFullyDozing() = + testScope.runTest { + burnInParameters = burnInParameters.copy(statusViewTop = 100) + val translationX by collectLastValue(underTest.translationX(burnInParameters)) + val translationY by collectLastValue(underTest.translationY(burnInParameters)) + val scale by collectLastValue(underTest.scale(burnInParameters)) + + // Set to dozing (on AOD) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.FINISHED + ), + validateStep = false, + ) + // Trigger a change to the burn-in model + burnInFlow.value = + BurnInModel( + translationX = 20, + translationY = 30, + scale = 0.5f, + ) + + assertThat(translationX).isEqualTo(20) + assertThat(translationY).isEqualTo(30) + assertThat(scale) + .isEqualTo( + BurnInScaleViewModel( + scale = 0.5f, + scaleClockOnly = true, + ) + ) + + // Set to the beginning of GONE->AOD transition + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + value = 0f, + transitionState = TransitionState.STARTED + ), + validateStep = false, + ) + assertThat(translationX).isEqualTo(0) + assertThat(translationY).isEqualTo(0) + assertThat(scale) + .isEqualTo( + BurnInScaleViewModel( + scale = 1f, + scaleClockOnly = true, + ) + ) + } + + @Test + fun translationAndScale_whenFullyDozing_staysOutOfTopInset() = + testScope.runTest { + burnInParameters = + burnInParameters.copy( + statusViewTop = 100, + topInset = 80, + ) + val translationX by collectLastValue(underTest.translationX(burnInParameters)) + val translationY by collectLastValue(underTest.translationY(burnInParameters)) + val scale by collectLastValue(underTest.scale(burnInParameters)) + + // Set to dozing (on AOD) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.FINISHED + ), + validateStep = false, + ) + + // Trigger a change to the burn-in model + burnInFlow.value = + BurnInModel( + translationX = 20, + translationY = -30, + scale = 0.5f, + ) + assertThat(translationX).isEqualTo(20) + // -20 instead of -30, due to inset of 80 + assertThat(translationY).isEqualTo(-20) + assertThat(scale) + .isEqualTo( + BurnInScaleViewModel( + scale = 0.5f, + scaleClockOnly = true, + ) + ) + + // Set to the beginning of GONE->AOD transition + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + value = 0f, + transitionState = TransitionState.STARTED + ), + validateStep = false, + ) + assertThat(translationX).isEqualTo(0) + assertThat(translationY).isEqualTo(0) + assertThat(scale) + .isEqualTo( + BurnInScaleViewModel( + scale = 1f, + scaleClockOnly = true, + ) + ) + } + + @Test + fun translationAndScale_useScaleOnly() = + testScope.runTest { + whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true) + + val translationX by collectLastValue(underTest.translationX(burnInParameters)) + val translationY by collectLastValue(underTest.translationY(burnInParameters)) + val scale by collectLastValue(underTest.scale(burnInParameters)) + + // Set to dozing (on AOD) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.FINISHED + ), + validateStep = false, + ) + + // Trigger a change to the burn-in model + burnInFlow.value = + BurnInModel( + translationX = 20, + translationY = 30, + scale = 0.5f, + ) + + assertThat(translationX).isEqualTo(0) + assertThat(translationY).isEqualTo(0) + assertThat(scale).isEqualTo(BurnInScaleViewModel(scale = 0.5f, scaleClockOnly = false)) + } + + @Test + fun alpha() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha) + + enterFromTopAnimationAlpha.value = 0.2f + assertThat(alpha).isEqualTo(0.2f) + + enterFromTopAnimationAlpha.value = 1f + assertThat(alpha).isEqualTo(1f) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt new file mode 100644 index 000000000000..7c3dc972cfd0 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.ui.viewmodel + +import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags as AConfigFlags +import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.notification.data.repository.fakeNotificationsKeyguardViewStateRepository +import com.android.systemui.statusbar.phone.dozeParameters +import com.android.systemui.statusbar.phone.screenOffAnimationController +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.ui.isAnimating +import com.android.systemui.util.ui.stopAnimating +import com.android.systemui.util.ui.value +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class KeyguardRootViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val screenOffAnimationController = kosmos.screenOffAnimationController + private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository + private val fakeNotificationsKeyguardViewStateRepository = + kosmos.fakeNotificationsKeyguardViewStateRepository + private val dozeParameters = kosmos.dozeParameters + private val underTest = kosmos.keyguardRootViewModel + + @Before + fun setUp() { + mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) + mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION) + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + } + + @Test + fun burnInLayerVisibility() = + testScope.runTest { + val burnInLayerVisibility by collectLastValue(underTest.burnInLayerVisibility) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 0f, + transitionState = TransitionState.STARTED + ), + validateStep = false, + ) + assertThat(burnInLayerVisibility).isEqualTo(View.VISIBLE) + } + + @Test + fun iconContainer_isNotVisible_notOnKeyguard_dontShowAodIconsWhenShade() = + testScope.runTest { + val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) + runCurrent() + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.OFF, + to = KeyguardState.GONE, + testScope, + ) + whenever(screenOffAnimationController.shouldShowAodIconsWhenShade()).thenReturn(false) + runCurrent() + + assertThat(isVisible?.value).isFalse() + assertThat(isVisible?.isAnimating).isFalse() + } + + @Test + fun iconContainer_isVisible_bypassEnabled() = + testScope.runTest { + val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) + runCurrent() + deviceEntryRepository.setBypassEnabled(true) + runCurrent() + + assertThat(isVisible?.value).isTrue() + } + + @Test + fun iconContainer_isNotVisible_pulseExpanding_notBypassing() = + testScope.runTest { + val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) + runCurrent() + fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(true) + deviceEntryRepository.setBypassEnabled(false) + runCurrent() + + assertThat(isVisible?.value).isEqualTo(false) + } + + @Test + fun iconContainer_isVisible_notifsFullyHidden_bypassEnabled() = + testScope.runTest { + val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) + runCurrent() + fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false) + deviceEntryRepository.setBypassEnabled(true) + fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true) + runCurrent() + + assertThat(isVisible?.value).isTrue() + assertThat(isVisible?.isAnimating).isTrue() + } + + @Test + fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled_aodDisabled() = + testScope.runTest { + val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) + runCurrent() + fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false) + deviceEntryRepository.setBypassEnabled(false) + whenever(dozeParameters.alwaysOn).thenReturn(false) + fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true) + runCurrent() + + assertThat(isVisible?.value).isTrue() + assertThat(isVisible?.isAnimating).isFalse() + } + + @Test + fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled_displayNeedsBlanking() = + testScope.runTest { + val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) + runCurrent() + fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false) + deviceEntryRepository.setBypassEnabled(false) + whenever(dozeParameters.alwaysOn).thenReturn(true) + whenever(dozeParameters.displayNeedsBlanking).thenReturn(true) + fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true) + runCurrent() + + assertThat(isVisible?.value).isTrue() + assertThat(isVisible?.isAnimating).isFalse() + } + + @Test + fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled() = + testScope.runTest { + val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) + runCurrent() + fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false) + deviceEntryRepository.setBypassEnabled(false) + whenever(dozeParameters.alwaysOn).thenReturn(true) + whenever(dozeParameters.displayNeedsBlanking).thenReturn(false) + fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true) + runCurrent() + + assertThat(isVisible?.value).isTrue() + assertThat(isVisible?.isAnimating).isTrue() + } + + @Test + fun isIconContainerVisible_stopAnimation() = + testScope.runTest { + val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) + runCurrent() + fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false) + deviceEntryRepository.setBypassEnabled(false) + whenever(dozeParameters.alwaysOn).thenReturn(true) + whenever(dozeParameters.displayNeedsBlanking).thenReturn(false) + fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true) + runCurrent() + + assertThat(isVisible?.isAnimating).isEqualTo(true) + isVisible?.stopAnimating() + runCurrent() + + assertThat(isVisible?.isAnimating).isEqualTo(false) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index d07836d3abce..74d309c1d359 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -26,6 +28,7 @@ import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -94,7 +97,6 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { KeyguardLongPressViewModel( interactor = mock(), ), - keyguardRoot = utils.keyguardRootViewModel(), notifications = utils.notificationsPlaceholderViewModel(), ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 224903ff36b8..efd4f9bdf449 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -153,7 +153,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { KeyguardLongPressViewModel( interactor = mock(), ), - keyguardRoot = utils.keyguardRootViewModel(), notifications = utils.notificationsPlaceholderViewModel(), ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index af5d48d9ae07..50836fe9ee51 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -39,6 +39,7 @@ import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener +import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel @@ -83,6 +84,7 @@ constructor( private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor, private val vibratorHelper: VibratorHelper, private val falsingManager: FalsingManager, + private val aodAlphaViewModel: AodAlphaViewModel, ) : CoreStartable { private var rootViewHandle: DisposableHandle? = null @@ -126,7 +128,7 @@ constructor( KeyguardIndicationAreaBinder.bind( notificationShadeWindowView.requireViewById(R.id.keyguard_indication_area), keyguardIndicationAreaViewModel, - keyguardRootViewModel, + aodAlphaViewModel, indicationController, ) } @@ -148,7 +150,6 @@ constructor( keyguardRootView, keyguardRootViewModel, configuration, - featureFlags, occludingAppDeviceEntryMessageViewModel, chipbarCoordinator, screenOffAnimationController, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt index 4c33d905b785..7c1368af652c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt @@ -23,8 +23,8 @@ import android.widget.TextView import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.Flags.keyguardBottomAreaRefactor +import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel -import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.statusbar.KeyguardIndicationController @@ -51,7 +51,7 @@ object KeyguardIndicationAreaBinder { fun bind( view: ViewGroup, viewModel: KeyguardIndicationAreaViewModel, - keyguardRootViewModel: KeyguardRootViewModel, + aodAlphaViewModel: AodAlphaViewModel, indicationController: KeyguardIndicationController, ): DisposableHandle { indicationController.setIndicationArea(view) @@ -69,7 +69,7 @@ object KeyguardIndicationAreaBinder { repeatOnLifecycle(Lifecycle.State.STARTED) { launch { if (keyguardBottomAreaRefactor()) { - keyguardRootViewModel.alpha.collect { alpha -> + aodAlphaViewModel.alpha.collect { alpha -> view.apply { this.importantForAccessibility = if (alpha == 0f) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index fad0370a85d7..2aebd99e3664 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -42,9 +42,9 @@ import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.TintedIcon import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor -import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel import com.android.systemui.lifecycle.repeatWhenAttached @@ -68,7 +68,10 @@ import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch /** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */ @@ -81,7 +84,6 @@ object KeyguardRootViewBinder { view: ViewGroup, viewModel: KeyguardRootViewModel, configuration: ConfigurationState, - featureFlags: FeatureFlagsClassic, occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel, chipbarCoordinator: ChipbarCoordinator, screenOffAnimationController: ScreenOffAnimationController, @@ -108,6 +110,8 @@ object KeyguardRootViewBinder { } } + val burnInParams = MutableStateFlow(BurnInParameters()) + val disposableHandle = view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { @@ -164,35 +168,41 @@ object KeyguardRootViewBinder { // large clock isn't added to burnInLayer due to its scale transition // so we also need to add translation to it here // same as translationX - viewModel.translationY.collect { y -> - childViews[burnInLayerId]?.translationY = y - childViews[largeClockId]?.translationY = y - } + burnInParams + .flatMapLatest { params -> viewModel.translationY(params) } + .collect { y -> + childViews[burnInLayerId]?.translationY = y + childViews[largeClockId]?.translationY = y + } } launch { - viewModel.translationX.collect { x -> - childViews[burnInLayerId]?.translationX = x - childViews[largeClockId]?.translationX = x - } + burnInParams + .flatMapLatest { params -> viewModel.translationX(params) } + .collect { x -> + childViews[burnInLayerId]?.translationX = x + childViews[largeClockId]?.translationX = x + } } launch { - viewModel.scale.collect { (scale, scaleClockOnly) -> - if (scaleClockOnly) { - // For clocks except weather clock, we have scale transition - // besides translate - childViews[largeClockId]?.let { - it.scaleX = scale - it.scaleY = scale + burnInParams + .flatMapLatest { params -> viewModel.scale(params) } + .collect { scaleViewModel -> + if (scaleViewModel.scaleClockOnly) { + // For clocks except weather clock, we have scale transition + // besides translate + childViews[largeClockId]?.let { + it.scaleX = scaleViewModel.scale + it.scaleY = scaleViewModel.scale + } + } else { + // For weather clock, large clock should have only scale + // transition with other parts in burnInLayer + childViews[burnInLayerId]?.scaleX = scaleViewModel.scale + childViews[burnInLayerId]?.scaleY = scaleViewModel.scale } - } else { - // For weather clock, large clock should have only scale - // transition with other parts in burnInLayer - childViews[burnInLayerId]?.scaleX = scale - childViews[burnInLayerId]?.scaleY = scale } - } } if (NotificationIconContainerRefactor.isEnabled) { @@ -274,10 +284,12 @@ object KeyguardRootViewBinder { } if (!migrateClocksToBlueprint()) { - viewModel.clockControllerProvider = clockControllerProvider + burnInParams.update { current -> + current.copy(clockControllerProvider = clockControllerProvider) + } } - onLayoutChangeListener = OnLayoutChange(viewModel) + onLayoutChangeListener = OnLayoutChange(viewModel, burnInParams) view.addOnLayoutChangeListener(onLayoutChangeListener) // Views will be added or removed after the call to bind(). This is needed to avoid many @@ -296,7 +308,9 @@ object KeyguardRootViewBinder { view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets -> val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout() - viewModel.topInset = insets.getInsetsIgnoringVisibility(insetTypes).top + burnInParams.update { current -> + current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top) + } insets } @@ -333,8 +347,10 @@ object KeyguardRootViewBinder { ) } - private class OnLayoutChange(private val viewModel: KeyguardRootViewModel) : - OnLayoutChangeListener { + private class OnLayoutChange( + private val viewModel: KeyguardRootViewModel, + private val burnInParams: MutableStateFlow<BurnInParameters>, + ) : OnLayoutChangeListener { override fun onLayoutChange( view: View, left: Int, @@ -355,7 +371,7 @@ object KeyguardRootViewBinder { } view.findViewById<View>(R.id.keyguard_status_view)?.let { statusView -> - viewModel.statusViewTop = statusView.top + burnInParams.update { current -> current.copy(statusViewTop = statusView.top) } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index 03e45fdbe75f..eb3afb7c9eec 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -367,7 +367,6 @@ constructor( keyguardRootView, keyguardRootViewModel, configuration, - featureFlags, occludingAppDeviceEntryMessageViewModel, chipbarCoordinator, screenOffAnimationController, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt index 66c137f7d75e..ea05c1d878b8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt @@ -25,8 +25,8 @@ import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea +import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel -import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.res.R import com.android.systemui.statusbar.KeyguardIndicationController import javax.inject.Inject @@ -37,7 +37,7 @@ class DefaultIndicationAreaSection constructor( private val context: Context, private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel, - private val keyguardRootViewModel: KeyguardRootViewModel, + private val aodAlphaViewModel: AodAlphaViewModel, private val indicationController: KeyguardIndicationController, ) : KeyguardSection() { private val indicationAreaViewId = R.id.keyguard_indication_area @@ -56,7 +56,7 @@ constructor( KeyguardIndicationAreaBinder.bind( constraintLayout.requireViewById(R.id.keyguard_indication_area), keyguardIndicationAreaViewModel, - keyguardRootViewModel, + aodAlphaViewModel, indicationController, ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt new file mode 100644 index 000000000000..d4ea728bbffb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart + +/** Models UI state for the alpha of the AOD (always-on display). */ +@SysUISingleton +class AodAlphaViewModel +@Inject +constructor( + keyguardInteractor: KeyguardInteractor, + keyguardTransitionInteractor: KeyguardTransitionInteractor, + occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, +) { + + /** The alpha level for the entire lockscreen while in AOD. */ + val alpha: Flow<Float> = + combine( + keyguardTransitionInteractor.transitionValue(KeyguardState.GONE).onStart { + emit(0f) + }, + merge( + keyguardInteractor.keyguardAlpha, + occludedToLockscreenTransitionViewModel.lockscreenAlpha, + ) + ) { transitionToGone, alpha -> + if (transitionToGone == 1f) { + // Ensures content is not visible when in GONE state + 0f + } else { + alpha + } + } + .distinctUntilChanged() +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt new file mode 100644 index 000000000000..780e323a96bc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.ui.viewmodel + +import android.util.MathUtils +import com.android.app.animation.Interpolators +import com.android.keyguard.KeyguardClockSwitch +import com.android.systemui.Flags +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.BurnInInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.BurnInModel +import com.android.systemui.plugins.clocks.ClockController +import com.android.systemui.res.R +import javax.inject.Inject +import javax.inject.Provider +import kotlin.math.max +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart + +/** + * Models UI state for elements that need to apply anti-burn-in tactics when showing in AOD + * (always-on display). + */ +@SysUISingleton +class AodBurnInViewModel +@Inject +constructor( + private val burnInInteractor: BurnInInteractor, + private val configurationInteractor: ConfigurationInteractor, + private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, + private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel, + private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, + private val keyguardClockViewModel: KeyguardClockViewModel, +) { + /** Alpha for elements that appear and move during the animation -> AOD */ + val alpha: Flow<Float> = goneToAodTransitionViewModel.enterFromTopAnimationAlpha + + /** Horizontal translation for elements that need to apply anti-burn-in tactics. */ + fun translationX( + params: BurnInParameters, + ): Flow<Float> { + return burnIn(params).map { it.translationX.toFloat() } + } + + /** Vertical translation for elements that need to apply anti-burn-in tactics. */ + fun translationY( + params: BurnInParameters, + ): Flow<Float> { + return configurationInteractor + .dimensionPixelSize(R.dimen.keyguard_enter_from_top_translation_y) + .flatMapLatest { enterFromTopAmount -> + combine( + keyguardInteractor.keyguardTranslationY.onStart { emit(0f) }, + burnIn(params).map { it.translationY.toFloat() }.onStart { emit(0f) }, + goneToAodTransitionViewModel + .enterFromTopTranslationY(enterFromTopAmount) + .onStart { emit(0f) }, + occludedToLockscreenTransitionViewModel.lockscreenTranslationY.onStart { + emit(0f) + }, + ) { + keyguardTransitionY, + burnInTranslationY, + goneToAodTransitionTranslationY, + occludedToLockscreenTransitionTranslationY -> + + // All values need to be combined for a smooth translation + keyguardTransitionY + + burnInTranslationY + + goneToAodTransitionTranslationY + + occludedToLockscreenTransitionTranslationY + } + } + .distinctUntilChanged() + } + + /** Scale for elements that need to apply anti-burn-in tactics. */ + fun scale( + params: BurnInParameters, + ): Flow<BurnInScaleViewModel> { + return burnIn(params).map { + BurnInScaleViewModel( + scale = it.scale, + scaleClockOnly = it.scaleClockOnly, + ) + } + } + + private fun burnIn( + params: BurnInParameters, + ): Flow<BurnInModel> { + return combine( + merge( + keyguardTransitionInteractor.goneToAodTransition.map { it.value }, + keyguardTransitionInteractor.dozeAmountTransition.map { it.value }, + ) + .map { dozeAmount -> Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount) }, + burnInInteractor.keyguardBurnIn, + ) { interpolated, burnIn -> + val useScaleOnly = + (clockController(params.clockControllerProvider) + ?.get() + ?.config + ?.useAlternateSmartspaceAODTransition + ?: false) && keyguardClockViewModel.clockSize.value == KeyguardClockSwitch.LARGE + + if (useScaleOnly) { + BurnInModel( + translationX = 0, + translationY = 0, + scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolated), + ) + } else { + // Ensure the desired translation doesn't encroach on the top inset + val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolated).toInt() + val translationY = + if (Flags.migrateClocksToBlueprint()) { + burnInY + } else { + max(params.topInset, params.statusViewTop + burnInY) - params.statusViewTop + } + + BurnInModel( + translationX = MathUtils.lerp(0, burnIn.translationX, interpolated).toInt(), + translationY = translationY, + scale = + MathUtils.lerp( + /* start= */ burnIn.scale, + /* stop= */ 1f, + /* amount= */ 1f - interpolated, + ), + scaleClockOnly = true, + ) + } + } + } + + private fun clockController( + provider: Provider<ClockController>?, + ): Provider<ClockController>? { + return if (Flags.migrateClocksToBlueprint()) { + Provider { keyguardClockViewModel.clock } + } else { + provider + } + } +} + +/** UI-sourced parameters to pass into the various methods of [AodBurnInViewModel]. */ +data class BurnInParameters( + val clockControllerProvider: Provider<ClockController>? = null, + /** System insets that keyguard needs to stay out of */ + val topInset: Int = 0, + /** Status view top, without translation added in */ + val statusViewTop: Int = 0, +) + +/** + * Models UI state of the scaling to apply to elements that need to be scaled for anti-burn-in + * purposes. + */ +data class BurnInScaleViewModel( + val scale: Float = 1f, + /** Whether the scale only applies to clock UI elements. */ + val scaleClockOnly: Boolean = false, +) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 26dace00ad76..5059e6be9080 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -18,27 +18,17 @@ package com.android.systemui.keyguard.ui.viewmodel import android.graphics.Point -import android.util.MathUtils import android.view.View.VISIBLE -import com.android.app.animation.Interpolators -import com.android.keyguard.KeyguardClockSwitch.LARGE -import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.Flags.newAodTransition import com.android.systemui.common.shared.model.NotificationContainerBounds -import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor -import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.BurnInModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN -import com.android.systemui.plugins.clocks.ClockController -import com.android.systemui.res.R import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.ScreenOffAnimationController @@ -49,51 +39,29 @@ import com.android.systemui.util.ui.AnimatedValue import com.android.systemui.util.ui.toAnimatedValueFlow import com.android.systemui.util.ui.zip import javax.inject.Inject -import javax.inject.Provider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.flow.onStart @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class KeyguardRootViewModel @Inject constructor( - configurationInteractor: ConfigurationInteractor, private val deviceEntryInteractor: DeviceEntryInteractor, private val dozeParameters: DozeParameters, private val keyguardInteractor: KeyguardInteractor, - private val keyguardTransitionInteractor: KeyguardTransitionInteractor, + keyguardTransitionInteractor: KeyguardTransitionInteractor, private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor, - private val burnInInteractor: BurnInInteractor, - private val keyguardClockViewModel: KeyguardClockViewModel, - private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel, - private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, - private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, + aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, screenOffAnimationController: ScreenOffAnimationController, - // TODO(b/310989341): remove after changing migrate_clocks_to_blueprint to aconfig - private val featureFlags: FeatureFlagsClassic, + private val aodBurnInViewModel: AodBurnInViewModel, + aodAlphaViewModel: AodAlphaViewModel, ) { - var clockControllerProvider: Provider<ClockController>? = null - get() { - if (migrateClocksToBlueprint()) { - return Provider { keyguardClockViewModel.clock } - } else { - return field - } - } - - /** System insets that keyguard needs to stay out of */ - var topInset: Int = 0 - /** Status view top, without translation added in */ - var statusViewTop: Int = 0 val burnInLayerVisibility: Flow<Int> = keyguardTransitionInteractor.startedKeyguardState @@ -110,96 +78,25 @@ constructor( keyguardInteractor.notificationContainerBounds /** An observable for the alpha level for the entire keyguard root view. */ - val alpha: Flow<Float> = - combine( - keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) }, - merge( - keyguardInteractor.keyguardAlpha, - occludedToLockscreenTransitionViewModel.lockscreenAlpha, - ) - ) { transitionToGone, alpha -> - if (transitionToGone == 1f) { - // Ensures content is not visible when in GONE state - 0f - } else { - alpha - } - } - .distinctUntilChanged() - - private fun burnIn(): Flow<BurnInModel> { - val dozingAmount: Flow<Float> = - merge( - keyguardTransitionInteractor.goneToAodTransition.map { it.value }, - keyguardTransitionInteractor.dozeAmountTransition.map { it.value }, - ) - - return combine(dozingAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn -> - val interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount) - val useScaleOnly = - (clockControllerProvider?.get()?.config?.useAlternateSmartspaceAODTransition - ?: false) && keyguardClockViewModel.clockSize.value == LARGE - if (useScaleOnly) { - BurnInModel( - translationX = 0, - translationY = 0, - scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolation), - ) - } else { - // Ensure the desired translation doesn't encroach on the top inset - val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolation).toInt() - val translationY = - if (migrateClocksToBlueprint()) { - burnInY - } else { - -(statusViewTop - Math.max(topInset, statusViewTop + burnInY)) - } - BurnInModel( - translationX = MathUtils.lerp(0, burnIn.translationX, interpolation).toInt(), - translationY = translationY, - scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolation), - scaleClockOnly = true, - ) - } - } - } + val alpha: Flow<Float> = aodAlphaViewModel.alpha /** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */ val lockscreenStateAlpha: Flow<Float> = aodToLockscreenTransitionViewModel.lockscreenAlpha /** For elements that appear and move during the animation -> AOD */ - val burnInLayerAlpha: Flow<Float> = goneToAodTransitionViewModel.enterFromTopAnimationAlpha + val burnInLayerAlpha: Flow<Float> = aodBurnInViewModel.alpha - val translationY: Flow<Float> = - configurationInteractor - .dimensionPixelSize(R.dimen.keyguard_enter_from_top_translation_y) - .flatMapLatest { enterFromTopAmount -> - combine( - keyguardInteractor.keyguardTranslationY.onStart { emit(0f) }, - burnIn().map { it.translationY.toFloat() }.onStart { emit(0f) }, - goneToAodTransitionViewModel - .enterFromTopTranslationY(enterFromTopAmount) - .onStart { emit(0f) }, - occludedToLockscreenTransitionViewModel.lockscreenTranslationY.onStart { - emit(0f) - }, - ) { - keyguardTransitionY, - burnInTranslationY, - goneToAodTransitionTranslationY, - occludedToLockscreenTransitionTranslationY -> - // All values need to be combined for a smooth translation - keyguardTransitionY + - burnInTranslationY + - goneToAodTransitionTranslationY + - occludedToLockscreenTransitionTranslationY - } - } - .distinctUntilChanged() + fun translationY(params: BurnInParameters): Flow<Float> { + return aodBurnInViewModel.translationY(params) + } - val translationX: Flow<Float> = burnIn().map { it.translationX.toFloat() } + fun translationX(params: BurnInParameters): Flow<Float> { + return aodBurnInViewModel.translationX(params) + } - val scale: Flow<Pair<Float, Boolean>> = burnIn().map { Pair(it.scale, it.scaleClockOnly) } + fun scale(params: BurnInParameters): Flow<BurnInScaleViewModel> { + return aodBurnInViewModel.scale(params) + } /** Is the notification icon container visible? */ val isNotifIconContainerVisible: Flow<AnimatedValue<Boolean>> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index 539db7fb1ae3..2b28a71b4a3d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -38,7 +38,6 @@ constructor( deviceEntryInteractor: DeviceEntryInteractor, communalInteractor: CommunalInteractor, val longPress: KeyguardLongPressViewModel, - val keyguardRoot: KeyguardRootViewModel, val notifications: NotificationsPlaceholderViewModel, ) { /** The key of the scene we should switch to when swiping up. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt index 8dd33d5e60bb..1205dceb49e9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt @@ -21,11 +21,11 @@ import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest -import com.android.systemui.res.R -import com.android.systemui.SysuiTestCase import com.android.systemui.Flags as AConfigFlags +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel -import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel +import com.android.systemui.res.R import com.android.systemui.statusbar.KeyguardIndicationController import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -38,8 +38,9 @@ import org.mockito.MockitoAnnotations @RunWith(JUnit4::class) @SmallTest class DefaultIndicationAreaSectionTest : SysuiTestCase() { + @Mock private lateinit var keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel - @Mock private lateinit var keyguardRootViewModel: KeyguardRootViewModel + @Mock private lateinit var aodAlphaViewModel: AodAlphaViewModel @Mock private lateinit var indicationController: KeyguardIndicationController private lateinit var underTest: DefaultIndicationAreaSection @@ -51,7 +52,7 @@ class DefaultIndicationAreaSectionTest : SysuiTestCase() { DefaultIndicationAreaSection( context, keyguardIndicationAreaViewModel, - keyguardRootViewModel, + aodAlphaViewModel, indicationController, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt deleted file mode 100644 index ee1be10607cf..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ /dev/null @@ -1,498 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -@file:OptIn(ExperimentalCoroutinesApi::class) - -package com.android.systemui.keyguard.ui.viewmodel - -import android.view.View -import androidx.test.filters.SmallTest -import com.android.systemui.Flags as AConfigFlags -import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION -import com.android.systemui.SysuiTestCase -import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository -import com.android.systemui.common.ui.domain.interactor.configurationInteractor -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository -import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor -import com.android.systemui.flags.featureFlagsClassic -import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.BurnInInteractor -import com.android.systemui.keyguard.domain.interactor.keyguardInteractor -import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.BurnInModel -import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.TransitionState -import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.kosmos.testScope -import com.android.systemui.plugins.clocks.ClockController -import com.android.systemui.statusbar.notification.data.repository.fakeNotificationsKeyguardViewStateRepository -import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor -import com.android.systemui.statusbar.phone.dozeParameters -import com.android.systemui.statusbar.phone.screenOffAnimationController -import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.whenever -import com.android.systemui.util.ui.isAnimating -import com.android.systemui.util.ui.stopAnimating -import com.android.systemui.util.ui.value -import com.google.common.truth.Truth.assertThat -import javax.inject.Provider -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.mockito.Answers -import org.mockito.Mock -import org.mockito.Mockito.RETURNS_DEEP_STUBS -import org.mockito.Mockito.anyInt -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(JUnit4::class) -class KeyguardRootViewModelTest : SysuiTestCase() { - private val kosmos = testKosmos() - private val testScope = kosmos.testScope - private val repository = kosmos.fakeKeyguardRepository - private val configurationRepository = kosmos.fakeConfigurationRepository - private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository - private val screenOffAnimationController = kosmos.screenOffAnimationController - private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository - private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor - private val fakeNotificationsKeyguardViewStateRepository = - kosmos.fakeNotificationsKeyguardViewStateRepository - private val dozeParameters = kosmos.dozeParameters - private lateinit var underTest: KeyguardRootViewModel - - @Mock private lateinit var burnInInteractor: BurnInInteractor - private val burnInFlow = MutableStateFlow(BurnInModel()) - - @Mock private lateinit var goneToAodTransitionViewModel: GoneToAodTransitionViewModel - private val enterFromTopAnimationAlpha = MutableStateFlow(0f) - - @Mock - private lateinit var aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel - @Mock - private lateinit var occludedToLockscreenTransitionViewModel: - OccludedToLockscreenTransitionViewModel - private val occludedToLockscreenTranslationY = MutableStateFlow(0f) - private val occludedToLockscreenAlpha = MutableStateFlow(0f) - - @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) - mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION) - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - - whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt())) - .thenReturn(emptyFlow<Float>()) - whenever(goneToAodTransitionViewModel.enterFromTopAnimationAlpha) - .thenReturn(enterFromTopAnimationAlpha) - - whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow) - - whenever(occludedToLockscreenTransitionViewModel.lockscreenTranslationY) - .thenReturn(occludedToLockscreenTranslationY) - whenever(occludedToLockscreenTransitionViewModel.lockscreenAlpha) - .thenReturn(occludedToLockscreenAlpha) - - underTest = - KeyguardRootViewModel( - configurationInteractor = kosmos.configurationInteractor, - deviceEntryInteractor = kosmos.deviceEntryInteractor, - dozeParameters = kosmos.dozeParameters, - keyguardInteractor = kosmos.keyguardInteractor, - keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor, - notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor, - burnInInteractor = burnInInteractor, - keyguardClockViewModel = kosmos.keyguardClockViewModel, - goneToAodTransitionViewModel = goneToAodTransitionViewModel, - aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, - occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel, - screenOffAnimationController = screenOffAnimationController, - // TODO(b/310989341): remove after change to aconfig - featureFlags = kosmos.featureFlagsClassic - ) - - underTest.clockControllerProvider = Provider { clockController } - } - - @Test - fun alpha() = - testScope.runTest { - val alpha by collectLastValue(underTest.alpha) - - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.OFF, - to = KeyguardState.LOCKSCREEN, - testScope = testScope, - ) - - repository.setKeyguardAlpha(0.1f) - assertThat(alpha).isEqualTo(0.1f) - repository.setKeyguardAlpha(0.5f) - assertThat(alpha).isEqualTo(0.5f) - repository.setKeyguardAlpha(0.2f) - assertThat(alpha).isEqualTo(0.2f) - repository.setKeyguardAlpha(0f) - assertThat(alpha).isEqualTo(0f) - occludedToLockscreenAlpha.value = 0.8f - assertThat(alpha).isEqualTo(0.8f) - } - - @Test - fun alphaWhenGoneEqualsZero() = - testScope.runTest { - val alpha by collectLastValue(underTest.alpha) - - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.GONE, - testScope = testScope, - ) - - repository.setKeyguardAlpha(0.1f) - assertThat(alpha).isEqualTo(0f) - repository.setKeyguardAlpha(0.5f) - assertThat(alpha).isEqualTo(0f) - repository.setKeyguardAlpha(1f) - assertThat(alpha).isEqualTo(0f) - } - - @Test - fun translationYInitialValueIsZero() = - testScope.runTest { - val translationY by collectLastValue(underTest.translationY) - assertThat(translationY).isEqualTo(0) - } - - @Test - fun translationAndScaleFromBurnInNotDozing() = - testScope.runTest { - val translationX by collectLastValue(underTest.translationX) - val translationY by collectLastValue(underTest.translationY) - val scale by collectLastValue(underTest.scale) - - // Set to not dozing (on lockscreen) - keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.AOD, - to = KeyguardState.LOCKSCREEN, - value = 1f, - transitionState = TransitionState.FINISHED - ), - validateStep = false, - ) - - // Trigger a change to the burn-in model - burnInFlow.value = - BurnInModel( - translationX = 20, - translationY = 30, - scale = 0.5f, - ) - - assertThat(translationX).isEqualTo(0) - assertThat(translationY).isEqualTo(0) - assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */)) - } - - @Test - fun translationAndScaleFromBurnFullyDozing() = - testScope.runTest { - val translationX by collectLastValue(underTest.translationX) - val translationY by collectLastValue(underTest.translationY) - val scale by collectLastValue(underTest.scale) - - underTest.statusViewTop = 100 - - // Set to dozing (on AOD) - keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - value = 1f, - transitionState = TransitionState.FINISHED - ), - validateStep = false, - ) - // Trigger a change to the burn-in model - burnInFlow.value = - BurnInModel( - translationX = 20, - translationY = 30, - scale = 0.5f, - ) - - assertThat(translationX).isEqualTo(20) - assertThat(translationY).isEqualTo(30) - assertThat(scale).isEqualTo(Pair(0.5f, true /* scaleClockOnly */)) - - // Set to the beginning of GONE->AOD transition - keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - value = 0f, - transitionState = TransitionState.STARTED - ), - validateStep = false, - ) - assertThat(translationX).isEqualTo(0) - assertThat(translationY).isEqualTo(0) - assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */)) - } - - @Test - fun translationAndScaleFromBurnFullyDozingStaysOutOfTopInset() = - testScope.runTest { - val translationX by collectLastValue(underTest.translationX) - val translationY by collectLastValue(underTest.translationY) - val scale by collectLastValue(underTest.scale) - - underTest.statusViewTop = 100 - underTest.topInset = 80 - - // Set to dozing (on AOD) - keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - value = 1f, - transitionState = TransitionState.FINISHED - ), - validateStep = false, - ) - - // Trigger a change to the burn-in model - burnInFlow.value = - BurnInModel( - translationX = 20, - translationY = -30, - scale = 0.5f, - ) - assertThat(translationX).isEqualTo(20) - // -20 instead of -30, due to inset of 80 - assertThat(translationY).isEqualTo(-20) - assertThat(scale).isEqualTo(Pair(0.5f, true /* scaleClockOnly */)) - - // Set to the beginning of GONE->AOD transition - keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - value = 0f, - transitionState = TransitionState.STARTED - ), - validateStep = false, - ) - assertThat(translationX).isEqualTo(0) - assertThat(translationY).isEqualTo(0) - assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */)) - } - - @Test - fun translationAndScaleFromBurnInUseScaleOnly() = - testScope.runTest { - whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true) - - val translationX by collectLastValue(underTest.translationX) - val translationY by collectLastValue(underTest.translationY) - val scale by collectLastValue(underTest.scale) - - // Set to dozing (on AOD) - keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - value = 1f, - transitionState = TransitionState.FINISHED - ), - validateStep = false, - ) - - // Trigger a change to the burn-in model - burnInFlow.value = - BurnInModel( - translationX = 20, - translationY = 30, - scale = 0.5f, - ) - - assertThat(translationX).isEqualTo(0) - assertThat(translationY).isEqualTo(0) - assertThat(scale).isEqualTo(Pair(0.5f, false /* scaleClockOnly */)) - } - - @Test - fun burnInLayerVisibility() = - testScope.runTest { - val burnInLayerVisibility by collectLastValue(underTest.burnInLayerVisibility) - - keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, - value = 0f, - transitionState = TransitionState.STARTED - ), - validateStep = false, - ) - assertThat(burnInLayerVisibility).isEqualTo(View.VISIBLE) - } - - @Test - fun burnInLayerAlpha() = - testScope.runTest { - val burnInLayerAlpha by collectLastValue(underTest.burnInLayerAlpha) - - enterFromTopAnimationAlpha.value = 0.2f - assertThat(burnInLayerAlpha).isEqualTo(0.2f) - - enterFromTopAnimationAlpha.value = 1f - assertThat(burnInLayerAlpha).isEqualTo(1f) - } - - @Test - fun iconContainer_isNotVisible_notOnKeyguard_dontShowAodIconsWhenShade() = - testScope.runTest { - val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) - runCurrent() - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.OFF, - to = KeyguardState.GONE, - testScope, - ) - whenever(screenOffAnimationController.shouldShowAodIconsWhenShade()).thenReturn(false) - runCurrent() - - assertThat(isVisible?.value).isFalse() - assertThat(isVisible?.isAnimating).isFalse() - } - - @Test - fun iconContainer_isVisible_bypassEnabled() = - testScope.runTest { - val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) - runCurrent() - deviceEntryRepository.setBypassEnabled(true) - runCurrent() - - assertThat(isVisible?.value).isTrue() - } - - @Test - fun iconContainer_isNotVisible_pulseExpanding_notBypassing() = - testScope.runTest { - val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) - runCurrent() - fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(true) - deviceEntryRepository.setBypassEnabled(false) - runCurrent() - - assertThat(isVisible?.value).isEqualTo(false) - } - - @Test - fun iconContainer_isVisible_notifsFullyHidden_bypassEnabled() = - testScope.runTest { - val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) - runCurrent() - fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false) - deviceEntryRepository.setBypassEnabled(true) - fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true) - runCurrent() - - assertThat(isVisible?.value).isTrue() - assertThat(isVisible?.isAnimating).isTrue() - } - - @Test - fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled_aodDisabled() = - testScope.runTest { - val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) - runCurrent() - fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false) - deviceEntryRepository.setBypassEnabled(false) - whenever(dozeParameters.alwaysOn).thenReturn(false) - fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true) - runCurrent() - - assertThat(isVisible?.value).isTrue() - assertThat(isVisible?.isAnimating).isFalse() - } - - @Test - fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled_displayNeedsBlanking() = - testScope.runTest { - val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) - runCurrent() - fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false) - deviceEntryRepository.setBypassEnabled(false) - whenever(dozeParameters.alwaysOn).thenReturn(true) - whenever(dozeParameters.displayNeedsBlanking).thenReturn(true) - fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true) - runCurrent() - - assertThat(isVisible?.value).isTrue() - assertThat(isVisible?.isAnimating).isFalse() - } - - @Test - fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled() = - testScope.runTest { - val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) - runCurrent() - fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false) - deviceEntryRepository.setBypassEnabled(false) - whenever(dozeParameters.alwaysOn).thenReturn(true) - whenever(dozeParameters.displayNeedsBlanking).thenReturn(false) - fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true) - runCurrent() - - assertThat(isVisible?.value).isTrue() - assertThat(isVisible?.isAnimating).isTrue() - } - - @Test - fun isIconContainerVisible_stopAnimation() = - testScope.runTest { - val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) - runCurrent() - fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false) - deviceEntryRepository.setBypassEnabled(false) - whenever(dozeParameters.alwaysOn).thenReturn(true) - whenever(dozeParameters.displayNeedsBlanking).thenReturn(false) - fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true) - runCurrent() - - assertThat(isVisible?.isAnimating).isEqualTo(true) - isVisible?.stopAnimating() - runCurrent() - - assertThat(isVisible?.isAnimating).isEqualTo(false) - } -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt index b0d941dc6c24..a9d89a37c542 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt @@ -26,7 +26,7 @@ import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -val Kosmos.burnInInteractor by Fixture { +var Kosmos.burnInInteractor by Fixture { BurnInInteractor( context = applicationContext, burnInHelperWrapper = burnInHelperWrapper, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractorKosmos.kt new file mode 100644 index 000000000000..a3955f7634eb --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractorKosmos.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.keyguard.data.repository.keyguardRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture + +val Kosmos.keyguardBottomAreaInteractor by Fixture { + KeyguardBottomAreaInteractor( + repository = keyguardRepository, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt new file mode 100644 index 000000000000..6b89e0f8901a --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.aodAlphaViewModel by Fixture { + AodAlphaViewModel( + keyguardInteractor = keyguardInteractor, + keyguardTransitionInteractor = keyguardTransitionInteractor, + occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt new file mode 100644 index 000000000000..35cfa89e56ed --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.common.ui.domain.interactor.configurationInteractor +import com.android.systemui.keyguard.domain.interactor.burnInInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.aodBurnInViewModel by Fixture { + AodBurnInViewModel( + burnInInteractor = burnInInteractor, + configurationInteractor = configurationInteractor, + keyguardInteractor = keyguardInteractor, + keyguardTransitionInteractor = keyguardTransitionInteractor, + goneToAodTransitionViewModel = goneToAodTransitionViewModel, + occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel, + keyguardClockViewModel = keyguardClockViewModel, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt index 14e2cff6a7a5..00ece1482236 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt @@ -25,7 +25,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import kotlinx.coroutines.ExperimentalCoroutinesApi -val Kosmos.goneToAodTransitionViewModel by Fixture { +var Kosmos.goneToAodTransitionViewModel by Fixture { GoneToAodTransitionViewModel( interactor = keyguardTransitionInteractor, deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt index 13ee74738437..933f50c36b7b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -18,10 +18,7 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor -import com.android.systemui.flags.FakeFeatureFlagsClassic -import com.android.systemui.keyguard.domain.interactor.burnInInteractor import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos @@ -33,18 +30,14 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.keyguardRootViewModel by Fixture { KeyguardRootViewModel( - configurationInteractor = configurationInteractor, deviceEntryInteractor = deviceEntryInteractor, dozeParameters = dozeParameters, keyguardInteractor = keyguardInteractor, keyguardTransitionInteractor = keyguardTransitionInteractor, notificationsKeyguardInteractor = notificationsKeyguardInteractor, - burnInInteractor = burnInInteractor, - goneToAodTransitionViewModel = goneToAodTransitionViewModel, aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, - occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel, screenOffAnimationController = screenOffAnimationController, - keyguardClockViewModel = keyguardClockViewModel, - featureFlags = FakeFeatureFlagsClassic(), + aodBurnInViewModel = aodBurnInViewModel, + aodAlphaViewModel = aodAlphaViewModel, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt index 5bbde2b1c419..93ecb7968ee2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt @@ -26,7 +26,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import kotlinx.coroutines.ExperimentalCoroutinesApi -val Kosmos.occludedToLockscreenTransitionViewModel by Fixture { +var Kosmos.occludedToLockscreenTransitionViewModel by Fixture { OccludedToLockscreenTransitionViewModel( interactor = keyguardTransitionInteractor, deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor, |