diff options
| author | 2024-04-22 17:21:44 +0000 | |
|---|---|---|
| committer | 2024-04-22 18:36:52 +0000 | |
| commit | 4d351aa2cde6b200673bb49b103ad68e7df61069 (patch) | |
| tree | 48dd01d95e36c9d593af69a198879df0c1ae5a5f | |
| parent | a8c826925c9e2db3b8a242cc88bbb6997b87cb85 (diff) | |
Add additional support for FromAlternateBouncer transitions
ALTERNATE_BOUNCER => GONE:
When the alternate bouncer is showing over an
occluding app, isKeyguardGoingAway does not update
to true. Therefore, we need to manually check
if biometric authentication has been handling
to trigger the ALT_BOUNCER => GONE transition when
the alt bouncer is showing over an occluding app.
ALTERNATE_BOUNCER => OCCLUDED
When the alternate bouncer is showing over an
occluding app, it's possible to navigate back
to the occluding application. Add support
for this transition in the AlternateBouncerTransitionInteractor
Test: Launch camera app over a locked lockscreen.
Press gallery icon to view photos. Observe alternate
bouncer. Authenticate with FP. Observe alternate
bouncer animates away.
Test: atest FromAlternateBouncerInteractorTest
Test: Launch camera app over a locked lockscreen.
Press gallery icon to view photos. Observe alternate bouncer.
Navigate back. Observe KeyguardTransition log back to OCCLUDED
Fixes: 336305546
Flag: None
Change-Id: Iadbbf8c28a350047a94797c18cbca2f4f4ddd833
3 files changed, 175 insertions, 6 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt index dfe56c839283..39aa6152f983 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -32,14 +33,21 @@ import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch +@ExperimentalCoroutinesApi @SysUISingleton class FromAlternateBouncerTransitionInteractor @Inject @@ -53,6 +61,7 @@ constructor( private val communalInteractor: CommunalInteractor, powerInteractor: PowerInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, + private val primaryBouncerInteractor: PrimaryBouncerInteractor, ) : TransitionInteractor( fromState = KeyguardState.ALTERNATE_BOUNCER, @@ -102,13 +111,14 @@ constructor( keyguardInteractor.primaryBouncerShowing, powerInteractor.isAwake, keyguardInteractor.isAodAvailable, - communalInteractor.isIdleOnCommunal + communalInteractor.isIdleOnCommunal, + keyguardInteractor.isKeyguardOccluded, ) .filterRelevantKeyguardStateAnd { (isAlternateBouncerShowing, isPrimaryBouncerShowing, _, _, _) -> !isAlternateBouncerShowing && !isPrimaryBouncerShowing } - .collect { (_, _, isAwake, isAodAvailable, isIdleOnCommunal) -> + .collect { (_, _, isAwake, isAodAvailable, isIdleOnCommunal, isOccluded) -> val to = if (!isAwake) { if (isAodAvailable) { @@ -119,6 +129,8 @@ constructor( } else { if (isIdleOnCommunal) { KeyguardState.GLANCEABLE_HUB + } else if (isOccluded) { + KeyguardState.OCCLUDED } else { KeyguardState.LOCKSCREEN } @@ -135,10 +147,19 @@ constructor( } scope.launch { - keyguardInteractor.isKeyguardGoingAway - .sampleUtil(finishedKeyguardState, ::Pair) - .collect { (isKeyguardGoingAway, keyguardState) -> - if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) { + merge( + keyguardInteractor.isKeyguardGoingAway.filter { it }.map {}, // map to Unit + keyguardInteractor.isKeyguardOccluded.flatMapLatest { keyguardOccluded -> + if (keyguardOccluded) { + primaryBouncerInteractor.keyguardAuthenticatedBiometricsHandled + } else { + emptyFlow() + } + } + ) + .sampleUtil(finishedKeyguardState) + .collect { keyguardState -> + if (keyguardState == KeyguardState.ALTERNATE_BOUNCER) { startTransitionTo(KeyguardState.GONE) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt new file mode 100644 index 000000000000..52bdf0eeb1c6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt @@ -0,0 +1,144 @@ +/* + * 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. + */ + +/* + * 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.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat +import com.android.systemui.kosmos.testScope +import com.android.systemui.power.data.repository.fakePowerRepository +import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.power.shared.model.WakefulnessState +import com.android.systemui.testKosmos +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.runner.RunWith +import org.mockito.Mockito +import org.mockito.Mockito.reset + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class FromAlternateBouncerTransitionInteractorTest : SysuiTestCase() { + private val kosmos = + testKosmos().apply { + this.fakeKeyguardTransitionRepository = Mockito.spy(FakeKeyguardTransitionRepository()) + } + private val testScope = kosmos.testScope + private lateinit var underTest: FromAlternateBouncerTransitionInteractor + private lateinit var transitionRepository: FakeKeyguardTransitionRepository + + @Before + fun setup() { + transitionRepository = kosmos.fakeKeyguardTransitionRepository + underTest = kosmos.fromAlternateBouncerTransitionInteractor + underTest.start() + } + + @Test + fun transitionToGone_keyguardOccluded_biometricAuthenticated() = + testScope.runTest { + transitionRepository.sendTransitionSteps( + from = KeyguardState.OCCLUDED, + to = KeyguardState.ALTERNATE_BOUNCER, + testScope + ) + reset(transitionRepository) + + kosmos.fakeKeyguardRepository.setKeyguardOccluded(true) + kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(true) + runCurrent() + kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(null) + runCurrent() + + assertThat(transitionRepository) + .startedTransition(from = KeyguardState.ALTERNATE_BOUNCER, to = KeyguardState.GONE) + } + + @Test + fun noTransition_keyguardNotOccluded_biometricAuthenticated() = + testScope.runTest { + transitionRepository.sendTransitionSteps( + from = KeyguardState.OCCLUDED, + to = KeyguardState.ALTERNATE_BOUNCER, + testScope + ) + reset(transitionRepository) + + kosmos.fakeKeyguardRepository.setKeyguardOccluded(false) + kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(true) + runCurrent() + kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(null) + runCurrent() + + assertThat(transitionRepository).noTransitionsStarted() + } + + @Test + fun transitionToOccluded() = + testScope.runTest { + kosmos.fakePowerRepository.updateWakefulness( + WakefulnessState.AWAKE, + WakeSleepReason.POWER_BUTTON, + WakeSleepReason.POWER_BUTTON, + false, + ) + kosmos.fakeKeyguardRepository.setKeyguardOccluded(true) + kosmos.fakeKeyguardBouncerRepository.setAlternateVisible(true) + transitionRepository.sendTransitionSteps( + from = KeyguardState.OCCLUDED, + to = KeyguardState.ALTERNATE_BOUNCER, + testScope + ) + reset(transitionRepository) + + kosmos.fakeKeyguardBouncerRepository.setAlternateVisible(false) + runCurrent() + testScope.testScheduler.advanceTimeBy(200) // advance past delay + + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.OCCLUDED + ) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt index 530cbedbdd0c..78a419f92495 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository import com.android.systemui.kosmos.Kosmos @@ -23,7 +24,9 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor +import kotlinx.coroutines.ExperimentalCoroutinesApi +@ExperimentalCoroutinesApi val Kosmos.fromAlternateBouncerTransitionInteractor by Kosmos.Fixture { FromAlternateBouncerTransitionInteractor( @@ -36,5 +39,6 @@ val Kosmos.fromAlternateBouncerTransitionInteractor by communalInteractor = communalInteractor, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + primaryBouncerInteractor = primaryBouncerInteractor, ) } |