diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt index d86b35d3b95a2f6b45880cc83d86f6ee705f23ba..92396e0bcdef87211a677d2a7f4e5a6108ac6beb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt @@ -92,25 +92,6 @@ class CommunalSceneStartableTest : SysuiTestCase() { } } - @Test - fun deviceDreaming_forceBlankScene() = - with(kosmos) { - testScope.runTest { - val scene by collectLastValue(communalInteractor.desiredScene) - - communalInteractor.onSceneChanged(CommunalSceneKey.Communal) - assertThat(scene).isEqualTo(CommunalSceneKey.Communal) - - fakeKeyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GLANCEABLE_HUB, - to = KeyguardState.DREAMING, - testScope = this - ) - - assertThat(scene).isEqualTo(CommunalSceneKey.Blank) - } - } - @Test fun deviceDocked_forceCommunalScene() = with(kosmos) { @@ -125,13 +106,6 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope = this ) assertThat(scene).isEqualTo(CommunalSceneKey.Communal) - - fakeKeyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GLANCEABLE_HUB, - to = KeyguardState.DREAMING, - testScope = this - ) - assertThat(scene).isEqualTo(CommunalSceneKey.Blank) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt index a6715dfcec24c4c36f4ce97c7f3abfb72574230a..c670506d9f045e1fcf433715a83a7940d80cec6f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt @@ -11,7 +11,6 @@ import com.android.systemui.complication.ComplicationHostViewController import com.android.systemui.dreams.ui.viewmodel.DreamOverlayViewModel import com.android.systemui.log.core.FakeLogBuffer import com.android.systemui.statusbar.BlurUtils -import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -46,7 +45,6 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() { @Mock private lateinit var hostViewController: ComplicationHostViewController @Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController @Mock private lateinit var stateController: DreamOverlayStateController - @Mock private lateinit var configController: ConfigurationController @Mock private lateinit var transitionViewModel: DreamOverlayViewModel private val logBuffer = FakeLogBuffer.Factory.create() private lateinit var controller: DreamOverlayAnimationsController @@ -62,7 +60,6 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() { stateController, DREAM_BLUR_RADIUS, transitionViewModel, - configController, DREAM_IN_BLUR_ANIMATION_DURATION, DREAM_IN_COMPLICATIONS_ANIMATION_DURATION, DREAM_IN_TRANSLATION_Y_DISTANCE, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelTest.kt index 4defe8a08d3d7ef7238ffc6ceecf4404294bee11..aba21c946e46f34aad0f6260bced2183d4c8be89 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelTest.kt @@ -19,12 +19,14 @@ 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.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectValues 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.res.R import com.android.systemui.testKosmos import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat @@ -38,6 +40,7 @@ class DreamingToGlanceableHubTransitionViewModelTest : SysuiTestCase() { val kosmos = testKosmos() val testScope = kosmos.testScope + val configurationRepository by lazy { kosmos.fakeConfigurationRepository } val underTest by lazy { kosmos.dreamingToGlanceableHubTransitionViewModel } @Test @@ -66,7 +69,12 @@ class DreamingToGlanceableHubTransitionViewModelTest : SysuiTestCase() { @Test fun dreamOverlayTranslationX() = testScope.runTest { - val values by collectValues(underTest.dreamOverlayTranslationX(100)) + configurationRepository.setDimensionPixelSize( + R.dimen.dreaming_to_hub_transition_dream_overlay_translation_x, + -100 + ) + + val values by collectValues(underTest.dreamOverlayTranslationX) assertThat(values).isEmpty() kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModelTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..11890c74a4181d6d5ba24fcd77e4b59925206eda --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModelTest.kt @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2024 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.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository +import com.android.systemui.coroutines.collectValues +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.res.R +import com.android.systemui.testKosmos +import com.google.common.collect.Range +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class GlanceableHubToDreamingTransitionViewModelTest : SysuiTestCase() { + val kosmos = testKosmos() + val testScope = kosmos.testScope + + val configurationRepository by lazy { kosmos.fakeConfigurationRepository } + val underTest by lazy { kosmos.glanceableHubToDreamingTransitionViewModel } + + @Test + fun dreamOverlayAlpha() = + testScope.runTest { + val values by collectValues(underTest.dreamOverlayAlpha) + assertThat(values).isEmpty() + + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( + listOf( + step(0f, TransitionState.STARTED), + step(0f), + // Should start running here... + step(0.1f), + step(0.5f), + // Up to here... + step(1f), + ), + testScope, + ) + + assertThat(values).hasSize(2) + values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } + } + + @Test + fun dreamOverlayTranslationX() = + testScope.runTest { + configurationRepository.setDimensionPixelSize( + R.dimen.hub_to_dreaming_transition_dream_overlay_translation_x, + 100 + ) + + val values by collectValues(underTest.dreamOverlayTranslationX) + assertThat(values).isEmpty() + + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( + listOf( + step(0f, TransitionState.STARTED), + step(0.3f), + step(0.6f), + ), + testScope, + ) + + assertThat(values).hasSize(3) + values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) } + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.DREAMING, + value = value, + transitionState = state, + ownerName = GlanceableHubToDreamingTransitionViewModelTest::class.java.simpleName + ) + } +} diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 5436642dc5a18a2131f1f3e232d7f354ae70296a..4be1deb3de1ccb072169df0a0c365eddcab59294 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1518,6 +1518,12 @@ <!-- GLANCEABLE_HUB -> LOCKSCREEN transition: Amount to shift lockscreen content on entering --> <dimen name="hub_to_lockscreen_transition_lockscreen_translation_x">824dp</dimen> + <!-- DREAMING -> GLANCEABLE_HUB transition: Amount to shift dream overlay on entering --> + <dimen name="dreaming_to_hub_transition_dream_overlay_translation_x">-824dp</dimen> + + <!-- GLANCEABLE_HUB -> DREAMING transition: Amount to shift dream overlay on entering --> + <dimen name="hub_to_dreaming_transition_dream_overlay_translation_x">824dp</dimen> + <!-- Distance that the full shade transition takes in order for media to fully transition to the shade --> <dimen name="lockscreen_shade_media_transition_distance">120dp</dimen> @@ -1861,7 +1867,6 @@ <dimen name="dream_overlay_y_offset">80dp</dimen> <dimen name="dream_overlay_entry_y_offset">40dp</dimen> <dimen name="dream_overlay_exit_y_offset">40dp</dimen> - <dimen name="dream_overlay_exit_x_offset">824dp</dimen> <dimen name="status_view_margin_horizontal">0dp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index f7ba5a44f4c94a63f6c9596375231e304197eb32..8397372e0735424cb8062598e29b4f67e8d47c48 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -88,7 +88,6 @@ constructor( val docked = dockManager.isDocked return when { - to == KeyguardState.DREAMING -> CommunalSceneKey.Blank docked && to == KeyguardState.LOCKSCREEN && from != KeyguardState.GLANCEABLE_HUB -> { CommunalSceneKey.Communal } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt index 9000da33312cf27b3d3070c223cf46b5c5fbaabd..b97bace9584f41e6aa61de8bcb0a18bbc6da68ce 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt @@ -40,7 +40,6 @@ import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.DreamLog import com.android.systemui.statusbar.BlurUtils import com.android.systemui.statusbar.CrossFadeHelper -import com.android.systemui.statusbar.policy.ConfigurationController import javax.inject.Inject import javax.inject.Named import kotlinx.coroutines.launch @@ -55,7 +54,6 @@ constructor( private val mOverlayStateController: DreamOverlayStateController, @Named(DreamOverlayModule.DREAM_BLUR_RADIUS) private val mDreamBlurRadius: Int, private val dreamOverlayViewModel: DreamOverlayViewModel, - private val configController: ConfigurationController, @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION) private val mDreamInBlurAnimDurationMs: Long, @Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION) diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt index dd67a4c8706c7e3cc8f915e25466ded4eacde892..bd99f4b8230e7af58a4375c722b4d5155e8b45fd 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt @@ -20,6 +20,7 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel import com.android.systemui.res.R import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -33,16 +34,16 @@ class DreamOverlayViewModel @Inject constructor( configurationInteractor: ConfigurationInteractor, - private val toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel, + toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel, + fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel, private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, ) { val dreamOverlayTranslationX: Flow<Float> = - configurationInteractor - .dimensionPixelSize(R.dimen.dream_overlay_exit_x_offset) - .flatMapLatest { px: Int -> - toGlanceableHubTransitionViewModel.dreamOverlayTranslationX(px) - } + merge( + toGlanceableHubTransitionViewModel.dreamOverlayTranslationX, + fromGlanceableHubTransitionInteractor.dreamOverlayTranslationX, + ) val dreamOverlayTranslationY: Flow<Float> = configurationInteractor @@ -55,6 +56,7 @@ constructor( merge( toLockscreenTransitionViewModel.dreamOverlayAlpha, toGlanceableHubTransitionViewModel.dreamOverlayAlpha, + fromGlanceableHubTransitionInteractor.dreamOverlayAlpha, ) val transitionEnded = toLockscreenTransitionViewModel.transitionEnded diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index c6594ef317d6e24c924b80a24aeabc1dcc212977..acfa107cc1f151eb56bb358e6fee102842eaf837 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import com.android.app.animation.Interpolators +import com.android.app.tracing.coroutines.launch import com.android.systemui.Flags.communalHub import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -64,12 +65,13 @@ constructor( private fun listenForDreamingToGlanceableHub() { if (!communalHub()) return - glanceableHubTransitions.listenForGlanceableHubTransition( - transitionName = "listenForDreamingToGlanceableHub", - transitionOwnerName = TAG, - fromState = KeyguardState.DREAMING, - toState = KeyguardState.GLANCEABLE_HUB, - ) + scope.launch("$TAG#listenForDreamingToGlanceableHub", mainDispatcher) { + glanceableHubTransitions.listenForGlanceableHubTransition( + transitionOwnerName = TAG, + fromState = KeyguardState.DREAMING, + toState = KeyguardState.GLANCEABLE_HUB, + ) + } } fun startToLockscreenTransition() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt index fbf195eb09520c90c628d2e43a327d050714279f..786c3c6697d94d44e5ec946e19cb56119114685a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt @@ -27,13 +27,16 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleMultiple +import com.android.systemui.util.kotlin.BooleanFlowOperators.and +import com.android.systemui.util.kotlin.BooleanFlowOperators.not import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext @SysUISingleton class FromGlanceableHubTransitionInteractor @@ -58,13 +61,12 @@ constructor( if (!Flags.communalHub()) { return } - listenForHubToLockscreen() + listenForHubToLockscreenOrDreaming() listenForHubToDozing() listenForHubToPrimaryBouncer() listenForHubToAlternateBouncer() listenForHubToOccluded() listenForHubToGone() - listenForHubToDreaming() } override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { @@ -82,13 +84,24 @@ constructor( * Listens for the glanceable hub transition to lock screen and directly drives the keyguard * transition. */ - private fun listenForHubToLockscreen() { - glanceableHubTransitions.listenForGlanceableHubTransition( - transitionName = "listenForHubToLockscreen", - transitionOwnerName = TAG, - fromState = KeyguardState.GLANCEABLE_HUB, - toState = KeyguardState.LOCKSCREEN, - ) + private fun listenForHubToLockscreenOrDreaming() { + scope.launch("$TAG#listenForGlanceableHubToLockscreenOrDream") { + keyguardInteractor.isDreaming.collectLatest { dreaming -> + withContext(mainDispatcher) { + val toState = + if (dreaming) { + KeyguardState.DREAMING + } else { + KeyguardState.LOCKSCREEN + } + glanceableHubTransitions.listenForGlanceableHubTransition( + transitionOwnerName = TAG, + fromState = KeyguardState.GLANCEABLE_HUB, + toState = toState, + ) + } + } + } } private fun listenForHubToPrimaryBouncer() { @@ -137,31 +150,15 @@ constructor( } } - private fun listenForHubToDreaming() { - val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING) - scope.launch("$TAG#listenForHubToDreaming") { - keyguardInteractor.isAbleToDream - .sampleMultiple(startedKeyguardTransitionStep, finishedKeyguardState) - .collect { (isAbleToDream, lastStartedTransition, finishedKeyguardState) -> - val isOnHub = finishedKeyguardState == KeyguardState.GLANCEABLE_HUB - val isTransitionInterruptible = - lastStartedTransition.to == KeyguardState.GLANCEABLE_HUB && - !invalidFromStates.contains(lastStartedTransition.from) - if (isAbleToDream && (isOnHub || isTransitionInterruptible)) { - startTransitionTo(KeyguardState.DREAMING) - } - } - } - } - private fun listenForHubToOccluded() { scope.launch { - keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect { - (isOccluded, keyguardState) -> - if (isOccluded && keyguardState == fromState) { - startTransitionTo(KeyguardState.OCCLUDED) + and(keyguardInteractor.isKeyguardOccluded, not(keyguardInteractor.isDreaming)) + .sample(startedKeyguardState, ::Pair) + .collect { (isOccludedAndNotDreaming, keyguardState) -> + if (isOccludedAndNotDreaming && keyguardState == fromState) { + startTransitionTo(KeyguardState.OCCLUDED) + } } - } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 40b2c638823d832a5b511ce0b8b0599f6f377e69..7263ae96b3a86c69a0fd3a2eff428ac6790172a3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -360,13 +360,13 @@ constructor( if (!com.android.systemui.Flags.communalHub()) { return } - - glanceableHubTransitions.listenForGlanceableHubTransition( - transitionName = "listenForLockscreenToGlanceableHub", - transitionOwnerName = TAG, - fromState = KeyguardState.LOCKSCREEN, - toState = KeyguardState.GLANCEABLE_HUB, - ) + scope.launch(mainDispatcher) { + glanceableHubTransitions.listenForGlanceableHubTransition( + transitionOwnerName = TAG, + fromState = KeyguardState.LOCKSCREEN, + toState = KeyguardState.GLANCEABLE_HUB, + ) + } } override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt index 809c0aee9882d2c4dd44c986a20c3769b9bf2837..6cb1eb493db3ac31f8ef2033b800583c6b9dd08f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt @@ -18,11 +18,9 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.launch import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalTransitionProgress import com.android.systemui.communal.shared.model.CommunalSceneKey -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState @@ -32,13 +30,11 @@ import com.android.systemui.util.kotlin.sample import java.util.UUID import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.flowOn class GlanceableHubTransitions @Inject constructor( - @Application private val scope: CoroutineScope, @Background private val bgDispatcher: CoroutineDispatcher, private val transitionInteractor: KeyguardTransitionInteractor, private val transitionRepository: KeyguardTransitionRepository, @@ -52,105 +48,101 @@ constructor( * externally. The progress is used for both transitions caused by user touch input or by * programmatic changes. */ - fun listenForGlanceableHubTransition( - transitionName: String, + suspend fun listenForGlanceableHubTransition( transitionOwnerName: String, fromState: KeyguardState, toState: KeyguardState, ) { val toScene = - if (toState == KeyguardState.GLANCEABLE_HUB) { - CommunalSceneKey.Communal - } else { + if (fromState == KeyguardState.GLANCEABLE_HUB) { CommunalSceneKey.Blank + } else { + CommunalSceneKey.Communal } var transitionId: UUID? = null - scope.launch("$transitionOwnerName#$transitionName") { - communalInteractor - .transitionProgressToScene(toScene) - .sample( - transitionInteractor.startedKeyguardTransitionStep.flowOn(bgDispatcher), - ::Pair - ) - .collect { pair -> - val (transitionProgress, lastStartedStep) = pair - val id = transitionId - if (id == null) { - // No transition started. - if ( - transitionProgress is CommunalTransitionProgress.Transition && - lastStartedStep.to == fromState - ) { - transitionId = - transitionRepository.startTransition( - TransitionInfo( - ownerName = transitionOwnerName, - from = fromState, - to = toState, - animator = null, // transition will be manually controlled - ) + communalInteractor + .transitionProgressToScene(toScene) + .sample( + transitionInteractor.startedKeyguardTransitionStep.flowOn(bgDispatcher), + ::Pair, + ) + .collect { (transitionProgress, lastStartedStep) -> + val id = transitionId + if (id == null) { + // No transition started. + if ( + transitionProgress is CommunalTransitionProgress.Transition && + lastStartedStep.to == fromState + ) { + transitionId = + transitionRepository.startTransition( + TransitionInfo( + ownerName = transitionOwnerName, + from = fromState, + to = toState, + animator = null, // transition will be manually controlled ) - } - } else { - if (lastStartedStep.to != toState) { - return@collect - } - // An existing `id` means a transition is started, and calls to - // `updateTransition` will control it until FINISHED or CANCELED - val nextState: TransitionState - val progressFraction: Float - when (transitionProgress) { - is CommunalTransitionProgress.Idle -> { - if (transitionProgress.scene == toScene) { - nextState = TransitionState.FINISHED - progressFraction = 1f - } else { - nextState = TransitionState.CANCELED - progressFraction = 0f - } - } - is CommunalTransitionProgress.Transition -> { - nextState = TransitionState.RUNNING - progressFraction = transitionProgress.progress - } - is CommunalTransitionProgress.OtherTransition -> { - // Shouldn't happen but if another transition starts during the - // current one, mark the current one as canceled. + ) + } + } else { + if (lastStartedStep.to != toState) { + return@collect + } + // An existing `id` means a transition is started, and calls to + // `updateTransition` will control it until FINISHED or CANCELED + val nextState: TransitionState + val progressFraction: Float + when (transitionProgress) { + is CommunalTransitionProgress.Idle -> { + if (transitionProgress.scene == toScene) { + nextState = TransitionState.FINISHED + progressFraction = 1f + } else { nextState = TransitionState.CANCELED progressFraction = 0f } } - transitionRepository.updateTransition( - id, - progressFraction, - nextState, - ) - - if ( - nextState == TransitionState.CANCELED || - nextState == TransitionState.FINISHED - ) { - transitionId = null + is CommunalTransitionProgress.Transition -> { + nextState = TransitionState.RUNNING + progressFraction = transitionProgress.progress } + is CommunalTransitionProgress.OtherTransition -> { + // Shouldn't happen but if another transition starts during the + // current one, mark the current one as canceled. + nextState = TransitionState.CANCELED + progressFraction = 0f + } + } + transitionRepository.updateTransition( + id, + progressFraction, + nextState, + ) - // If canceled, just put the state back. - if (nextState == TransitionState.CANCELED) { - transitionRepository.startTransition( - TransitionInfo( - ownerName = transitionOwnerName, - from = toState, - to = fromState, - animator = - ValueAnimator().apply { - interpolator = Interpolators.LINEAR - duration = 0 - } - ) + if ( + nextState == TransitionState.CANCELED || + nextState == TransitionState.FINISHED + ) { + transitionId = null + } + + // If canceled, just put the state back. + if (nextState == TransitionState.CANCELED) { + transitionRepository.startTransition( + TransitionInfo( + ownerName = transitionOwnerName, + from = toState, + to = fromState, + animator = + ValueAnimator().apply { + interpolator = Interpolators.LINEAR + duration = 0 + } ) - } + ) } } - } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt index 374a93275ff25d11f81ad5b7ab7d75f1835d0e02..c64f277b519a4338497feef1ccfdc726ff38b8da 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt @@ -17,18 +17,26 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.res.R import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class DreamingToGlanceableHubTransitionViewModel @Inject -constructor(animationFlow: KeyguardTransitionAnimationFlow) { +constructor( + animationFlow: KeyguardTransitionAnimationFlow, + configurationInteractor: ConfigurationInteractor, +) { private val transitionAnimation = animationFlow.setup( @@ -37,14 +45,18 @@ constructor(animationFlow: KeyguardTransitionAnimationFlow) { to = KeyguardState.GLANCEABLE_HUB, ) - fun dreamOverlayTranslationX(translatePx: Int): Flow<Float> { - return transitionAnimation.sharedFlow( - duration = TO_GLANCEABLE_HUB_DURATION, - onStep = { it * -translatePx }, - interpolator = EMPHASIZED, - name = "DREAMING->GLANCEABLE_HUB: overlayTranslationX", - ) - } + val dreamOverlayTranslationX: Flow<Float> = + configurationInteractor + .dimensionPixelSize(R.dimen.dreaming_to_hub_transition_dream_overlay_translation_x) + .flatMapLatest { translatePx -> + transitionAnimation.sharedFlow( + duration = TO_GLANCEABLE_HUB_DURATION, + onStep = { value -> value * translatePx }, + interpolator = EMPHASIZED, + onCancel = { 0f }, + name = "DREAMING->GLANCEABLE_HUB: overlayTranslationX", + ) + } val dreamOverlayAlpha: Flow<Float> = transitionAnimation.sharedFlow( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..478c4faa1be303fd7fa1b09a1afc5c5d9cf17e3f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 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.viewmodel + +import com.android.app.animation.Interpolators +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.res.R +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest + +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class GlanceableHubToDreamingTransitionViewModel +@Inject +constructor( + animationFlow: KeyguardTransitionAnimationFlow, + configurationInteractor: ConfigurationInteractor, +) { + + private val transitionAnimation = + animationFlow.setup( + duration = FROM_GLANCEABLE_HUB_DURATION, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.DREAMING, + ) + + val dreamOverlayAlpha: Flow<Float> = + transitionAnimation.sharedFlow( + duration = 167.milliseconds, + startTime = 167.milliseconds, + onStep = { it }, + name = "GLANCEABLE_HUB->DREAMING: dreamOverlayAlpha", + ) + + val dreamOverlayTranslationX: Flow<Float> = + configurationInteractor + .dimensionPixelSize(R.dimen.hub_to_dreaming_transition_dream_overlay_translation_x) + .flatMapLatest { translatePx: Int -> + transitionAnimation.sharedFlow( + duration = FROM_GLANCEABLE_HUB_DURATION, + onStep = { value -> -translatePx + value * translatePx }, + interpolator = Interpolators.EMPHASIZED, + onCancel = { -translatePx.toFloat() }, + name = "GLANCEABLE_HUB->LOCKSCREEN: dreamOverlayTranslationX" + ) + } + + private companion object { + val FROM_GLANCEABLE_HUB_DURATION = 1.seconds + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index abd42387cc2eff1f12a094ef41a5b985b9f5fa81..69cd173f4253ffa472f34f18f0dcd837de6897f3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -129,7 +129,6 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val glanceableHubTransitions = GlanceableHubTransitions( - scope = testScope, bgDispatcher = kosmos.testDispatcher, transitionInteractor = transitionInteractor, transitionRepository = transitionRepository, @@ -1812,26 +1811,40 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { @Test fun glanceableHubToDreaming() = testScope.runTest { - // GIVEN a device that is not dreaming or dozing - keyguardRepository.setDreamingWithOverlay(false) + // GIVEN that we are dreaming and not dozing + keyguardRepository.setDreaming(true) keyguardRepository.setDozeTransitionModel( DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) ) runCurrent() // GIVEN a prior transition has run to GLANCEABLE_HUB - runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB) + runTransitionAndSetWakefulness(KeyguardState.DREAMING, KeyguardState.GLANCEABLE_HUB) + runCurrent() - // WHEN the device begins to dream - keyguardRepository.setDreamingWithOverlay(true) - advanceTimeBy(100L) + // WHEN a transition away from glanceable hub starts + val currentScene = CommunalSceneKey.Communal + val targetScene = CommunalSceneKey.Blank + + val transitionState = + MutableStateFlow<ObservableCommunalTransitionState>( + ObservableCommunalTransitionState.Transition( + fromScene = currentScene, + toScene = targetScene, + progress = flowOf(0f, 0.1f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + communalInteractor.setTransitionState(transitionState) + runCurrent() assertThat(transitionRepository) .startedTransition( ownerName = FromGlanceableHubTransitionInteractor::class.simpleName, from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.DREAMING, - animatorAssertion = { it.isNotNull() }, + animatorAssertion = { it.isNull() }, // transition should be manually animated ) coroutineContext.cancelChildren() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt index 55885bf58acc40370c44b5bb81856bb1ee3b0737..5dd50731c58a3f8b4b24df9f2ecd340059b1f169 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt @@ -19,13 +19,11 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher val Kosmos.glanceableHubTransitions by Kosmos.Fixture { GlanceableHubTransitions( - scope = applicationCoroutineScope, bgDispatcher = testDispatcher, transitionRepository = keyguardTransitionRepository, transitionInteractor = keyguardTransitionInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt similarity index 87% rename from packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt rename to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt index b37085957d7ef3a18a96a543228a59ce7b8743d3..00741eb69c62109ad3349109b26e1f78c870e716 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt @@ -16,12 +16,14 @@ package com.android.systemui.keyguard.ui.viewmodel +import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos val Kosmos.dreamingToGlanceableHubTransitionViewModel by Kosmos.Fixture { DreamingToGlanceableHubTransitionViewModel( + configurationInteractor = configurationInteractor, animationFlow = keyguardTransitionAnimationFlow, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModelKosmos.kt new file mode 100644 index 0000000000000000000000000000000000000000..1302f155d93b455b1823ae45875d367be8fad9b6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModelKosmos.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 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.viewmodel + +import com.android.systemui.common.ui.domain.interactor.configurationInteractor +import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow +import com.android.systemui.kosmos.Kosmos + +val Kosmos.glanceableHubToDreamingTransitionViewModel by + Kosmos.Fixture { + GlanceableHubToDreamingTransitionViewModel( + configurationInteractor = configurationInteractor, + animationFlow = keyguardTransitionAnimationFlow, + ) + }