diff options
| author | 2024-02-16 10:50:49 -0500 | |
|---|---|---|
| committer | 2024-02-16 23:23:48 -0500 | |
| commit | 4a00ae2e3de5020035a4709b93043d4eb8393510 (patch) | |
| tree | 115e10d0173cd5685e9339d121b861c43b8417ea | |
| parent | 7ef3905b674d8d6fd0c5157a98e471a3b2c565b5 (diff) | |
Polish hub <-> lockscreen motion
Adds scrim fade and lockscreen x-translation when transitioning to/from
the glanceable hub
Bug: 325101978
Test: atest SystemUiRoboTest
Flag: ACONFIG com.android.systemui.communal_hub STAGING
Change-Id: I064253796cda128f3ee7a9d3b01cd5284e90765d
21 files changed, 513 insertions, 88 deletions
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt index aa567364d130..76931a2b4d41 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -22,12 +22,15 @@ import android.view.View import android.view.WindowInsets import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.LifecycleOwner +import com.android.compose.theme.LocalAndroidColorScheme import com.android.compose.theme.PlatformTheme import com.android.compose.ui.platform.DensityAwareComposeView import com.android.internal.policy.ScreenDecorationsUtils @@ -89,12 +92,18 @@ object ComposeFacade : BaseComposeFacade { ) { activity.setContent { PlatformTheme { - CommunalHub( - viewModel = viewModel, - onOpenWidgetPicker = onOpenWidgetPicker, - widgetConfigurator = widgetConfigurator, - onEditDone = onEditDone, - ) + Box( + modifier = + Modifier.fillMaxSize() + .background(LocalAndroidColorScheme.current.outlineVariant), + ) { + CommunalHub( + viewModel = viewModel, + onOpenWidgetPicker = onOpenWidgetPicker, + widgetConfigurator = widgetConfigurator, + onEditDone = onEditDone, + ) + } } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index bc85513ae296..be5aa8a4c3b9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -1,6 +1,7 @@ package com.android.systemui.communal.ui.compose import androidx.compose.animation.core.tween +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable @@ -12,6 +13,7 @@ import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.FixedSizeEdgeDetector +import com.android.compose.animation.scene.LowestZIndexScenePicker import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneScope @@ -21,6 +23,7 @@ import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.observableTransitionState import com.android.compose.animation.scene.transitions import com.android.compose.animation.scene.updateSceneTransitionLayoutState +import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.communal.ui.compose.extensions.allowGestures @@ -31,16 +34,25 @@ import kotlinx.coroutines.flow.transform object Communal { object Elements { + val Scrim = ElementKey("Scrim", scenePicker = LowestZIndexScenePicker) val Content = ElementKey("CommunalContent") } } val sceneTransitions = transitions { - from(TransitionSceneKey.Blank, to = TransitionSceneKey.Communal) { - spec = tween(durationMillis = 500) - + to(TransitionSceneKey.Communal) { + spec = tween(durationMillis = 1000) + translate(Communal.Elements.Content, Edge.Right) + timestampRange(startMillis = 167, endMillis = 334) { + fade(Communal.Elements.Scrim) + fade(Communal.Elements.Content) + } + } + to(TransitionSceneKey.Blank) { + spec = tween(durationMillis = 1000) translate(Communal.Elements.Content, Edge.Right) - fade(Communal.Elements.Content) + timestampRange(endMillis = 167) { fade(Communal.Elements.Content) } + timestampRange(startMillis = 167, endMillis = 334) { fade(Communal.Elements.Scrim) } } } @@ -111,6 +123,12 @@ private fun SceneScope.CommunalScene( viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier, ) { + Box( + modifier = + Modifier.element(Communal.Elements.Scrim) + .fillMaxSize() + .background(LocalAndroidColorScheme.current.outlineVariant), + ) Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 090750e046a7..cddd4fa1c33d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -141,7 +141,6 @@ fun CommunalHub( modifier = modifier .fillMaxSize() - .background(LocalAndroidColorScheme.current.outlineVariant) .pointerInput(gridState, contentOffset, contentListState) { // If not in edit mode, don't allow selecting items. if (!viewModel.isEditMode) return@pointerInput diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt new file mode 100644 index 000000000000..64125f139a2e --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt @@ -0,0 +1,114 @@ +/* + * 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.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class GlanceableHubToLockscreenTransitionViewModelTest : SysuiTestCase() { + val kosmos = testKosmos() + val testScope = kosmos.testScope + + val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + val configurationRepository = kosmos.fakeConfigurationRepository + val underTest by lazy { kosmos.glanceableHubToLockscreenTransitionViewModel } + + @Test + fun lockscreenFadeIn() = + testScope.runTest { + val values by collectValues(underTest.keyguardAlpha) + assertThat(values).containsExactly(0f) + + keyguardTransitionRepository.sendTransitionSteps( + listOf( + step(0f, TransitionState.STARTED), + // Should start running here... + step(0.1f), + step(0.2f), + step(0.3f), + step(0.4f), + // ...up to here + step(0.5f), + step(0.6f), + step(0.7f), + step(0.8f), + step(1f), + ), + testScope, + ) + + assertThat(values).hasSize(5) + values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } + } + + @Test + fun lockscreenTranslationX() = + testScope.runTest { + configurationRepository.setDimensionPixelSize( + R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x, + 100 + ) + val values by collectValues(underTest.keyguardTranslationX) + assertThat(values).isEmpty() + + keyguardTransitionRepository.sendTransitionSteps( + listOf( + step(0f, TransitionState.STARTED), + step(0f), + step(0.3f), + step(0.5f), + step(1f), + ), + testScope, + ) + + assertThat(values).hasSize(5) + values.forEach { assertThat(it.value).isIn(Range.closed(-100f, 0f)) } + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.LOCKSCREEN, + value = value, + transitionState = state, + ownerName = this::class.java.simpleName + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelTest.kt new file mode 100644 index 000000000000..241d0b818193 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelTest.kt @@ -0,0 +1,115 @@ +/* + * 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.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class LockscreenToGlanceableHubTransitionViewModelTest : SysuiTestCase() { + val kosmos = testKosmos() + val testScope = kosmos.testScope + + val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + val configurationRepository = kosmos.fakeConfigurationRepository + val underTest by lazy { kosmos.lockscreenToGlanceableHubTransitionViewModel } + + @Test + fun lockscreenFadeOut() = + testScope.runTest { + val values by collectValues(underTest.keyguardAlpha) + assertThat(values).containsExactly(1f) + + keyguardTransitionRepository.sendTransitionSteps( + listOf( + // Should start running here + step(0f, TransitionState.STARTED), + step(0.1f), + step(0.2f), + // ...up to here + step(0.3f), + step(0.4f), + step(0.5f), + step(0.6f), + step(0.7f), + step(0.8f), + // ...up to here + step(1f), + ), + testScope, + ) + + assertThat(values).hasSize(4) + values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } + } + + @Test + fun lockscreenTranslationX() = + testScope.runTest { + configurationRepository.setDimensionPixelSize( + R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x, + -100 + ) + val values by collectValues(underTest.keyguardTranslationX) + assertThat(values).isEmpty() + + keyguardTransitionRepository.sendTransitionSteps( + listOf( + step(0f, TransitionState.STARTED), + step(0f), + step(0.3f), + step(0.5f), + step(1f), + ), + testScope, + ) + + assertThat(values).hasSize(5) + values.forEach { assertThat(it.value).isIn(Range.closed(-100f, 0f)) } + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GLANCEABLE_HUB, + value = value, + transitionState = state, + ownerName = this::class.java.simpleName + ) + } +} diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 382cef78d40a..65120a99a6b5 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1514,6 +1514,12 @@ <!-- The amount of vertical offset for the keyguard during the full shade transition. --> <dimen name="lockscreen_shade_keyguard_transition_vertical_offset">0dp</dimen> + <!-- LOCKSCREEN -> GLANCEABLE_HUB transition: Amount to shift lockscreen content on entering --> + <dimen name="lockscreen_to_hub_transition_lockscreen_translation_x">-824dp</dimen> + + <!-- GLANCEABLE_HUB -> LOCKSCREEN transition: Amount to shift lockscreen content on entering --> + <dimen name="hub_to_lockscreen_transition_lockscreen_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> 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 93cd04475561..fbf195eb0952 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 @@ -30,7 +30,7 @@ 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.sample import javax.inject.Inject -import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -70,7 +70,11 @@ constructor( override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { return ValueAnimator().apply { interpolator = Interpolators.LINEAR - duration = DEFAULT_DURATION.inWholeMilliseconds + duration = + when (toState) { + KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION + else -> DEFAULT_DURATION + }.inWholeMilliseconds } } @@ -175,7 +179,7 @@ constructor( companion object { const val TAG = "FromGlanceableHubTransitionInteractor" - val DEFAULT_DURATION = 400.milliseconds + val DEFAULT_DURATION = 1.seconds val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION } } 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 e028b89fd30c..40b2c638823d 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 @@ -38,6 +38,7 @@ import com.android.systemui.util.kotlin.sample import java.util.UUID import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -380,6 +381,7 @@ constructor( KeyguardState.AOD -> TO_AOD_DURATION KeyguardState.DOZING -> TO_DOZING_DURATION KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> TO_DREAMING_HOSTED_DURATION + KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION else -> DEFAULT_DURATION }.inWholeMilliseconds } @@ -395,6 +397,6 @@ constructor( val TO_AOD_DURATION = 500.milliseconds val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION val TO_GONE_DURATION = DEFAULT_DURATION - val TO_GLANCEABLE_HUB_DURATION = DEFAULT_DURATION + val TO_GLANCEABLE_HUB_DURATION = 1.seconds } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt index 8b278cdb9a6c..b8ba09801ee8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt @@ -185,13 +185,16 @@ constructor( return getOrCreateFlow(edge) .map { step -> StateToValue( - step.transitionState, - when (step.transitionState) { - STARTED -> stepToValue(step) - RUNNING -> stepToValue(step) - CANCELED -> onCancel?.invoke() - FINISHED -> onFinish?.invoke() - } + from = step.from, + to = step.to, + transitionState = step.transitionState, + value = + when (step.transitionState) { + STARTED -> stepToValue(step) + RUNNING -> stepToValue(step) + CANCELED -> onCancel?.invoke() + FINISHED -> onFinish?.invoke() + } ) .also { logger.logTransitionStep(name, step, it.value) } } @@ -208,6 +211,10 @@ constructor( } data class StateToValue( + val from: KeyguardState? = null, + val to: KeyguardState? = null, val transitionState: TransitionState = TransitionState.FINISHED, val value: Float? = 0f, -) +) { + fun isToOrFrom(state: KeyguardState) = from == state || to == state +} 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 c58a03c05a09..ed488487f524 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 @@ -43,6 +43,7 @@ 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.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel @@ -101,6 +102,10 @@ object KeyguardRootViewBinder { val burnInLayerId = R.id.burn_in_layer val aodNotificationIconContainerId = R.id.aod_notification_icon_container val largeClockId = R.id.lockscreen_clock_view_large + val indicationArea = R.id.keyguard_indication_area + val startButton = R.id.start_button + val endButton = R.id.end_button + val lockIcon = R.id.lock_icon_view if (keyguardBottomAreaRefactor()) { view.setOnTouchListener { _, event -> @@ -200,10 +205,29 @@ object KeyguardRootViewBinder { launch { burnInParams .flatMapLatest { params -> viewModel.translationX(params) } - .collect { x -> - childViews[burnInLayerId]?.translationX = x - childViews[largeClockId]?.translationX = x - childViews[aodNotificationIconContainerId]?.translationX = x + .collect { state -> + val px = state.value ?: return@collect + when { + state.isToOrFrom(KeyguardState.AOD) -> { + childViews[largeClockId]?.translationX = px + childViews[burnInLayerId]?.translationX = px + childViews[aodNotificationIconContainerId] + ?.translationX = px + } + state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> { + for ((key, childView) in childViews.entries) { + when (key) { + indicationArea, + startButton, + endButton, + lockIcon -> { + // Do not move these views + } + else -> childView.translationX = px + } + } + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt index 6aa2ecabae75..e5b596419efe 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt @@ -16,13 +16,22 @@ 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.domain.interactor.FromGlanceableHubTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.StateToValue +import com.android.systemui.res.R import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart /** * Breaks down GLANCEABLE_HUB->LOCKSCREEN transition into discrete steps for corresponding views to @@ -33,32 +42,43 @@ import kotlinx.coroutines.flow.Flow class GlanceableHubToLockscreenTransitionViewModel @Inject constructor( + configurationInteractor: ConfigurationInteractor, animationFlow: KeyguardTransitionAnimationFlow, ) { private val transitionAnimation = animationFlow.setup( - duration = FromGlanceableHubTransitionInteractor.TO_LOCKSCREEN_DURATION, + duration = TO_LOCKSCREEN_DURATION, from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.LOCKSCREEN, ) - // TODO(b/315205222): implement full animation spec instead of just a simple fade. val keyguardAlpha: Flow<Float> = - transitionAnimation.sharedFlow( - duration = FromGlanceableHubTransitionInteractor.TO_LOCKSCREEN_DURATION, - onStep = { it }, - onFinish = { 1f }, - onCancel = { 0f }, - name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardAlpha", - ) + transitionAnimation + .sharedFlow( + duration = 167.milliseconds, + startTime = 167.milliseconds, + onStep = { it }, + onFinish = { 1f }, + onCancel = { 0f }, + name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardAlpha", + ) + .onStart { emit(0f) } - // TODO(b/315205216): implement full animation spec instead of just a simple fade. - val notificationAlpha: Flow<Float> = - transitionAnimation.sharedFlow( - duration = FromGlanceableHubTransitionInteractor.TO_LOCKSCREEN_DURATION, - onStep = { it }, - onFinish = { 1f }, - onCancel = { 0f }, - name = "GLANCEABLE_HUB->LOCKSCREEN: notificationAlpha", - ) + val keyguardTranslationX: Flow<StateToValue> = + configurationInteractor + .dimensionPixelSize(R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x) + .flatMapLatest { translatePx: Int -> + transitionAnimation.sharedFlowWithState( + duration = TO_LOCKSCREEN_DURATION, + onStep = { value -> -translatePx + value * translatePx }, + interpolator = EMPHASIZED, + onCancel = { -translatePx.toFloat() }, + name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardTranslationX" + ) + } + + val notificationAlpha: Flow<Float> = keyguardAlpha + + val notificationTranslationX: Flow<Float> = + keyguardTranslationX.map { it.value }.filterNotNull() } 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 f790d356620d..35119338fb32 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 @@ -32,6 +32,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionState.STARTED +import com.android.systemui.keyguard.ui.StateToValue import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.ScreenOffAnimationController @@ -165,8 +166,12 @@ constructor( return aodBurnInViewModel.translationY(params) } - fun translationX(params: BurnInParameters): Flow<Float> { - return aodBurnInViewModel.translationX(params) + fun translationX(params: BurnInParameters): Flow<StateToValue> { + return merge( + aodBurnInViewModel.translationX(params).map { StateToValue(to = AOD, value = it) }, + lockscreenToGlanceableHubTransitionViewModel.keyguardTranslationX, + glanceableHubToLockscreenTransitionViewModel.keyguardTranslationX, + ) } fun scale(params: BurnInParameters): Flow<BurnInScaleViewModel> { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt index 3afa49e50167..978e71e2a825 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt @@ -16,13 +16,22 @@ 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.domain.interactor.FromLockscreenTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.StateToValue +import com.android.systemui.res.R import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart /** * Breaks down LOCKSCREEN->GLANCEABLE_HUB transition into discrete steps for corresponding views to @@ -33,6 +42,7 @@ import kotlinx.coroutines.flow.Flow class LockscreenToGlanceableHubTransitionViewModel @Inject constructor( + configurationInteractor: ConfigurationInteractor, animationFlow: KeyguardTransitionAnimationFlow, ) { private val transitionAnimation = @@ -42,23 +52,35 @@ constructor( to = KeyguardState.GLANCEABLE_HUB, ) - // TODO(b/315205222): implement full animation spec instead of just a simple fade. val keyguardAlpha: Flow<Float> = - transitionAnimation.sharedFlow( - duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION, - onStep = { 1f - it }, - onFinish = { 0f }, - onCancel = { 1f }, - name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardAlpha", - ) + transitionAnimation + .sharedFlow( + duration = 167.milliseconds, + onStep = { 1f - it }, + onFinish = { 0f }, + onCancel = { 1f }, + name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardAlpha", + ) + .onStart { emit(1f) } - // TODO(b/315205216): implement full animation spec instead of just a simple fade. - val notificationAlpha: Flow<Float> = - transitionAnimation.sharedFlow( - duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION, - onStep = { 1f - it }, - onFinish = { 0f }, - onCancel = { 1f }, - name = "LOCKSCREEN->GLANCEABLE_HUB: notificationAlpha", - ) + val keyguardTranslationX: Flow<StateToValue> = + configurationInteractor + .dimensionPixelSize(R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x) + .flatMapLatest { translatePx: Int -> + transitionAnimation.sharedFlowWithState( + duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION, + onStep = { value -> value * translatePx }, + // Move notifications back to their original position since they can be + // accessed from the shade. + onFinish = { 0f }, + onCancel = { 0f }, + interpolator = EMPHASIZED, + name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardTranslationX" + ) + } + + val notificationAlpha: Flow<Float> = keyguardAlpha + + val notificationTranslationX: Flow<Float> = + keyguardTranslationX.map { it.value }.filterNotNull() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index d2ff266319d0..715505d81d71 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -1039,6 +1039,11 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.setTranslationY(translationY); } + /** Set view x-translation */ + public void setTranslationX(float translationX) { + mView.setTranslationX(translationX); + } + public int indexOfChild(View view) { return mView.indexOfChild(view); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt index daea8af8f334..77e146b98242 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt @@ -144,6 +144,8 @@ object SharedNotificationContainerBinder { .collect { y -> controller.setTranslationY(y) } } + launch { viewModel.translationX.collect { x -> controller.translationX = x } } + if (!sceneContainerFlags.isEnabled()) { launch { viewModel.expansionAlpha(viewState).collect { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index f325157c598d..6b949a3461e7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -363,14 +363,9 @@ constructor( lockscreenToGlanceableHubRunning, glanceableHubToLockscreenRunning, merge( - lockscreenToGlanceableHubTransitionViewModel.notificationAlpha, - glanceableHubToLockscreenTransitionViewModel.notificationAlpha, - ) - .onStart { - // Transition flows don't emit a value on start, kick things off so the - // combine starts. - emit(1f) - } + lockscreenToGlanceableHubTransitionViewModel.notificationAlpha, + glanceableHubToLockscreenTransitionViewModel.notificationAlpha, + ) ) { lockscreenToGlanceableHubRunning, glanceableHubToLockscreenRunning, alpha -> if (isOnGlanceableHubWithoutShade) { // Notifications should not be visible on the glanceable hub. @@ -409,6 +404,16 @@ constructor( } /** + * The container may need to be translated in the x direction as the keyguard fades out, such as + * when swiping open the glanceable hub from the lockscreen. + */ + val translationX: Flow<Float> = + merge( + lockscreenToGlanceableHubTransitionViewModel.notificationTranslationX, + glanceableHubToLockscreenTransitionViewModel.notificationTranslationX, + ) + + /** * When on keyguard, there is limited space to display notifications so calculate how many could * be shown. Otherwise, there is no limit since the vertical space will be scrollable. * diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt index 0e9197ef8ac1..f0607f4b70e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt @@ -22,7 +22,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue 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.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope @@ -51,8 +52,8 @@ class KeyguardTransitionAnimationFlowTest : SysuiTestCase() { underTest = animationFlow.setup( duration = 1000.milliseconds, - from = KeyguardState.GONE, - to = KeyguardState.DREAMING, + from = GONE, + to = DREAMING, ) } @@ -192,17 +193,65 @@ class KeyguardTransitionAnimationFlowTest : SysuiTestCase() { runCurrent() repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - assertThat(animationValues).isEqualTo(StateToValue(TransitionState.STARTED, 0f)) + assertThat(animationValues) + .isEqualTo( + StateToValue( + from = GONE, + to = DREAMING, + transitionState = TransitionState.STARTED, + value = 0f + ) + ) repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING)) - assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 0.6f)) + assertThat(animationValues) + .isEqualTo( + StateToValue( + from = GONE, + to = DREAMING, + transitionState = TransitionState.RUNNING, + value = 0.6f + ) + ) repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING)) - assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 1.2f)) + assertThat(animationValues) + .isEqualTo( + StateToValue( + from = GONE, + to = DREAMING, + transitionState = TransitionState.RUNNING, + value = 1.2f + ) + ) repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING)) - assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 1.6f)) + assertThat(animationValues) + .isEqualTo( + StateToValue( + from = GONE, + to = DREAMING, + transitionState = TransitionState.RUNNING, + value = 1.6f + ) + ) repository.sendTransitionStep(step(1f, TransitionState.RUNNING)) - assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 2f)) + assertThat(animationValues) + .isEqualTo( + StateToValue( + from = GONE, + to = DREAMING, + transitionState = TransitionState.RUNNING, + value = 2f + ) + ) repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) - assertThat(animationValues).isEqualTo(StateToValue(TransitionState.FINISHED, null)) + assertThat(animationValues) + .isEqualTo( + StateToValue( + from = GONE, + to = DREAMING, + transitionState = TransitionState.FINISHED, + value = null + ) + ) } @Test @@ -251,8 +300,8 @@ class KeyguardTransitionAnimationFlowTest : SysuiTestCase() { state: TransitionState = TransitionState.RUNNING ): TransitionStep { return TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.DREAMING, + from = GONE, + to = DREAMING, value = value, transitionState = state, ownerName = "GoneToDreamingTransitionViewModelTest" diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt index bfa84335d670..716c40d59ccf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt @@ -59,7 +59,14 @@ class GoneToAodTransitionViewModelTest : SysuiTestCase() { // The animation should only start > .4f way through repository.sendTransitionStep(step(0f, TransitionState.STARTED)) assertThat(enterFromTopTranslationY) - .isEqualTo(StateToValue(TransitionState.STARTED, pixels)) + .isEqualTo( + StateToValue( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + transitionState = TransitionState.STARTED, + value = pixels + ) + ) repository.sendTransitionStep(step(.55f)) assertThat(enterFromTopTranslationY!!.value ?: -1f).isIn(Range.closed(pixels, 0f)) @@ -70,7 +77,14 @@ class GoneToAodTransitionViewModelTest : SysuiTestCase() { // At the end, the translation should be complete and set to zero repository.sendTransitionStep(step(1f)) assertThat(enterFromTopTranslationY) - .isEqualTo(StateToValue(TransitionState.RUNNING, 0f)) + .isEqualTo( + StateToValue( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + transitionState = TransitionState.RUNNING, + value = 0f + ) + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index b9b872254b06..3e0082c5aed7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -52,6 +52,7 @@ import com.android.systemui.statusbar.policy.splitShadeStateController import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever +import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -274,8 +275,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { ) ) runCurrent() - // Expected alpha is inverse of progress as notifications are fading away - assertThat(alpha).isEqualTo(1 - progress) + assertThat(alpha).isIn(Range.closed(0f, 1f)) // Finish transition to glanceable hub keyguardTransitionRepository.sendTransitionStep( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt index 28fce77b75b7..b1c21b8fa6cf 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt @@ -18,6 +18,7 @@ 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 import com.android.systemui.kosmos.Kosmos.Fixture @@ -26,6 +27,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi @OptIn(ExperimentalCoroutinesApi::class) val Kosmos.glanceableHubToLockscreenTransitionViewModel by Fixture { GlanceableHubToLockscreenTransitionViewModel( + configurationInteractor = configurationInteractor, animationFlow = keyguardTransitionAnimationFlow, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt index 9fe4ea347f63..471381f7a13f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt @@ -18,13 +18,16 @@ 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 import com.android.systemui.kosmos.Kosmos.Fixture import kotlinx.coroutines.ExperimentalCoroutinesApi +@ExperimentalCoroutinesApi val Kosmos.lockscreenToGlanceableHubTransitionViewModel by Fixture { LockscreenToGlanceableHubTransitionViewModel( + configurationInteractor = configurationInteractor, animationFlow = keyguardTransitionAnimationFlow, ) } |