diff options
9 files changed, 196 insertions, 26 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt index 36458ede9506..15c9cf73d51d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt @@ -22,6 +22,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase 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.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.shared.model.KeyguardState.GONE @@ -37,6 +38,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertEquals +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test @@ -242,7 +244,8 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { @Test fun transitionValue() = testScope.runTest { - val startedSteps by collectValues(underTest.transitionValue(state = DOZING)) + resetTransitionValueReplayCache(setOf(AOD, DOZING, LOCKSCREEN)) + val transitionValues by collectValues(underTest.transitionValue(state = DOZING)) val toSteps = listOf( @@ -266,12 +269,14 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { runCurrent() } - assertThat(startedSteps).isEqualTo(listOf(0f, 0.5f, 1f, 1f, 0.5f, 0f)) + assertThat(transitionValues).isEqualTo(listOf(0f, 0.5f, 1f, 1f, 0.5f, 0f)) } @Test fun transitionValue_canceled_toAnotherState() = testScope.runTest { + resetTransitionValueReplayCache(setOf(AOD, GONE, LOCKSCREEN)) + val transitionValuesGone by collectValues(underTest.transitionValue(state = GONE)) val transitionValuesAod by collectValues(underTest.transitionValue(state = AOD)) val transitionValuesLs by collectValues(underTest.transitionValue(state = LOCKSCREEN)) @@ -297,6 +302,8 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { @Test fun transitionValue_canceled_backToOriginalState() = testScope.runTest { + resetTransitionValueReplayCache(setOf(AOD, GONE)) + val transitionValuesGone by collectValues(underTest.transitionValue(state = GONE)) val transitionValuesAod by collectValues(underTest.transitionValue(state = AOD)) @@ -1395,4 +1402,8 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { testScope.runCurrent() } } + + private fun resetTransitionValueReplayCache(states: Set<KeyguardState>) { + states.forEach { (underTest.transitionValue(it) as MutableSharedFlow).resetReplayCache() } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index cc7ebe96ba11..509a82da8eb9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -585,6 +585,12 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { notificationCount = 25 sharedNotificationContainerInteractor.notificationStackChanged() assertThat(maxNotifications).isEqualTo(25) + + // Also ensure another collection starts with the same value. As an example, folding + // then unfolding will restart the coroutine and it must get the last value immediately. + val newMaxNotifications by + collectLastValue(underTest.getMaxNotifications(calculateSpace)) + assertThat(newMaxNotifications).isEqualTo(25) } @Test @@ -760,6 +766,41 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test + fun alphaDoesNotUpdateWhileOcclusionTransitionIsRunning() = + testScope.runTest { + val viewState = ViewStateAccessor() + val alpha by collectLastValue(underTest.keyguardAlpha(viewState)) + + showLockscreen() + // OCCLUDED transition gets to 90% complete + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + transitionState = TransitionState.STARTED, + value = 0f, + ) + ) + runCurrent() + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + transitionState = TransitionState.RUNNING, + value = 0.9f, + ) + ) + runCurrent() + + // At this point, alpha should be zero + assertThat(alpha).isEqualTo(0f) + + // An attempt to override by the shade should be ignored + shadeRepository.setQsExpansion(0.5f) + assertThat(alpha).isEqualTo(0f) + } + + @Test fun alphaWhenGoneIsSetToOne() = testScope.runTest { val viewState = ViewStateAccessor() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index f10327e02240..a0ab8699ca48 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -218,5 +218,6 @@ constructor( private val DEFAULT_DURATION = 500.milliseconds val TO_LOCKSCREEN_DURATION = 933.milliseconds val TO_AOD_DURATION = DEFAULT_DURATION + val TO_DOZING_DURATION = DEFAULT_DURATION } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 0cd7d18b2342..804b630c3288 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -85,9 +85,11 @@ constructor( private fun getTransitionValueFlow(state: KeyguardState): MutableSharedFlow<Float> { return transitionValueCache.getOrPut(state) { MutableSharedFlow<Float>( - extraBufferCapacity = 2, - onBufferOverflow = BufferOverflow.DROP_OLDEST - ) + replay = 1, + extraBufferCapacity = 2, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + .also { it.tryEmit(0f) } } } 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 5337ca3b9be1..e8313a9f739c 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 @@ -102,6 +102,7 @@ constructor( private val lockscreenToPrimaryBouncerTransitionViewModel: LockscreenToPrimaryBouncerTransitionViewModel, private val occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel, + private val occludedToDozingTransitionViewModel: OccludedToDozingTransitionViewModel, private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, private val primaryBouncerToAodTransitionViewModel: PrimaryBouncerToAodTransitionViewModel, private val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel, @@ -228,6 +229,7 @@ constructor( lockscreenToOccludedTransitionViewModel.lockscreenAlpha, lockscreenToPrimaryBouncerTransitionViewModel.lockscreenAlpha, occludedToAodTransitionViewModel.lockscreenAlpha, + occludedToDozingTransitionViewModel.lockscreenAlpha, occludedToLockscreenTransitionViewModel.lockscreenAlpha, primaryBouncerToAodTransitionViewModel.lockscreenAlpha, primaryBouncerToGoneTransitionViewModel.lockscreenAlpha, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt new file mode 100644 index 000000000000..91554e3e914a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt @@ -0,0 +1,52 @@ +/* + * 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.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow + +/** + * Breaks down OCCLUDED->DOZING transition into discrete steps for corresponding views to consume. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class OccludedToDozingTransitionViewModel +@Inject +constructor( + animationFlow: KeyguardTransitionAnimationFlow, +) { + private val transitionAnimation = + animationFlow.setup( + duration = FromOccludedTransitionInteractor.TO_DOZING_DURATION, + from = KeyguardState.OCCLUDED, + to = KeyguardState.DOZING, + ) + + /** Lockscreen views alpha */ + val lockscreenAlpha: Flow<Float> = + transitionAnimation.sharedFlow( + startTime = 233.milliseconds, + duration = 250.milliseconds, + onStep = { it }, + ) +} 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 f767b9997864..9df6f93df6a5 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 @@ -33,6 +33,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB 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.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED @@ -61,6 +62,8 @@ import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTran import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor +import com.android.systemui.util.kotlin.BooleanFlowOperators.and +import com.android.systemui.util.kotlin.BooleanFlowOperators.or import com.android.systemui.util.kotlin.FlowDumperImpl import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine import javax.inject.Inject @@ -79,7 +82,6 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transformWhile import kotlinx.coroutines.isActive @@ -123,6 +125,7 @@ constructor( ) : FlowDumperImpl(dumpManager) { private val statesForConstrainedNotifications: Set<KeyguardState> = setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER) + private val statesForHiddenKeyguard: Set<KeyguardState> = setOf(GONE, OCCLUDED) /** * Is either shade/qs expanded? This intentionally does not use the [ShadeInteractor] version, @@ -194,8 +197,12 @@ constructor( ) { constrainedNotificationState, transitioningToOrFromLockscreen -> constrainedNotificationState || transitioningToOrFromLockscreen } - .shareIn(scope = applicationScope, started = SharingStarted.Eagerly) - .dumpWhileCollecting("isOnLockscreen") + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = false + ) + .dumpValue("isOnLockscreen") /** Are we purely on the keyguard without the shade/qs? */ val isOnLockscreenWithoutShade: Flow<Boolean> = @@ -385,31 +392,54 @@ constructor( .onStart { emit(1f) } .dumpWhileCollecting("alphaForShadeAndQsExpansion") - private val isGoneTransitionRunning: Flow<Boolean> = + private fun toFlowArray( + states: Set<KeyguardState>, + flow: (KeyguardState) -> Flow<Boolean> + ): Array<Flow<Boolean>> { + return states.map { flow(it) }.toTypedArray() + } + + private val isTransitioningToHiddenKeyguard: Flow<Boolean> = flow { while (currentCoroutineContext().isActive) { emit(false) - // Ensure start where GONE is inactive - keyguardTransitionInteractor.transitionValue(GONE).first { it == 0f } - // Wait for a GONE transition to begin - keyguardTransitionInteractor.transitionStepsToState(GONE).first { - it.value > 0f && it.transitionState == RUNNING - } + // Ensure states are inactive to start + and( + *toFlowArray(statesForHiddenKeyguard) { state -> + keyguardTransitionInteractor.transitionValue(state).map { it == 0f } + } + ) + .first { it } + // Wait for a qualifying transition to begin + or( + *toFlowArray(statesForHiddenKeyguard) { state -> + keyguardTransitionInteractor + .transitionStepsToState(state) + .map { it.value > 0f && it.transitionState == RUNNING } + .onStart { emit(false) } + } + ) + .first { it } emit(true) - // Now await the signal that SHADE state has been reached or the GONE transition - // was reversed. Until SHADE state has been replaced and merged with GONE, it is - // the only source of when it is considered safe to reset alpha to 1f for HUNs. + // Now await the signal that SHADE state has been reached or the transition was + // reversed. Until SHADE state has been replaced it is the only source of when + // it is considered safe to reset alpha to 1f for HUNs. combine( keyguardInteractor.statusBarState, - // Emit -1f on start to make sure the flow runs - keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(-1f) } - ) { statusBarState, goneValue -> - statusBarState == SHADE || goneValue == 0f + and( + *toFlowArray(statesForHiddenKeyguard) { state -> + keyguardTransitionInteractor.transitionValue(state).map { + it == 0f + } + } + ) + ) { statusBarState, stateIsReversed -> + statusBarState == SHADE || stateIsReversed } .first { it } } } - .dumpWhileCollecting("goneTransitionInProgress") + .dumpWhileCollecting("isTransitioningToHiddenKeyguard") fun keyguardAlpha(viewState: ViewStateAccessor): Flow<Float> { // All transition view models are mututally exclusive, and safe to merge @@ -440,7 +470,7 @@ constructor( // shade expansion or swipe to dismiss combineTransform( isOnLockscreenWithoutShade, - isGoneTransitionRunning, + isTransitioningToHiddenKeyguard, shadeCollapseFadeIn, alphaForShadeAndQsExpansion, keyguardInteractor.dismissAlpha.dumpWhileCollecting( @@ -448,7 +478,7 @@ constructor( ), ) { isOnLockscreenWithoutShade, - isGoneTransitionRunning, + isTransitioningToHiddenKeyguard, shadeCollapseFadeIn, alphaForShadeAndQsExpansion, dismissAlpha -> @@ -456,7 +486,7 @@ constructor( if (!shadeCollapseFadeIn && dismissAlpha != null) { emit(dismissAlpha) } - } else if (!isGoneTransitionRunning) { + } else if (!isTransitioningToHiddenKeyguard) { emit(alphaForShadeAndQsExpansion) } }, 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 b91aafea9c38..f856d2700270 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 @@ -62,6 +62,7 @@ val Kosmos.keyguardRootViewModel by Fixture { lockscreenToPrimaryBouncerTransitionViewModel = lockscreenToPrimaryBouncerTransitionViewModel, occludedToAodTransitionViewModel = occludedToAodTransitionViewModel, + occludedToDozingTransitionViewModel = occludedToDozingTransitionViewModel, occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel, primaryBouncerToAodTransitionViewModel = primaryBouncerToAodTransitionViewModel, primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModelKosmos.kt new file mode 100644 index 000000000000..a05e60672ef7 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModelKosmos.kt @@ -0,0 +1,30 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.occludedToDozingTransitionViewModel by Fixture { + OccludedToDozingTransitionViewModel( + animationFlow = keyguardTransitionAnimationFlow, + ) +} |