Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4d351aa2 authored by Beverly's avatar Beverly
Browse files

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
parent a8c82692
Loading
Loading
Loading
Loading
+27 −6
Original line number Diff line number Diff line
@@ -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)
                    }
                }
+144 −0
Original line number Diff line number Diff line
/*
 * 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
                )
        }
}
+4 −0
Original line number Diff line number Diff line
@@ -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,
        )
    }