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

Commit e09e9a17 authored by Alejandro Nijamkin's avatar Alejandro Nijamkin
Browse files

[flexiglass] Trigger face unlock when overscrolling bouncer.

When dragging up on the bouncer, there's nowhere for scene navigation to
go; instead, overscroll kicks in and the bouncer UI is translated
slightly upwards. When this happens, trigger a face unlock attempt.

Fix: 299343636
Test: unit test added
Test: manually verified that scrolling upwards on the bouncer triggers
face unlock and actually unlocks the device and transitions to the gone
scene
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT

Change-Id: I96aa5460821cd81a4a8808348ac633ab736a694b
parent 566c9a0c
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import com.android.systemui.classifier.falsingManager
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -289,6 +290,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
                centralSurfaces = mock(),
                headsUpInteractor = kosmos.headsUpNotificationInteractor,
                occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
                faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor,
            )
        startable.start()

+24 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.falsingManager
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -133,6 +134,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
                centralSurfaces = centralSurfaces,
                headsUpInteractor = kosmos.headsUpNotificationInteractor,
                occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
                faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor,
            )
    }

@@ -1079,6 +1081,28 @@ class SceneContainerStartableTest : SysuiTestCase() {
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
        }

    @Test
    fun handleBouncerOverscroll() =
        testScope.runTest {
            val currentScene by collectLastValue(sceneInteractor.currentScene)
            val transitionStateFlow = prepareState()
            underTest.start()
            emulateSceneTransition(transitionStateFlow, toScene = Scenes.Bouncer)
            assertThat(currentScene).isEqualTo(Scenes.Bouncer)

            transitionStateFlow.value =
                ObservableTransitionState.Transition(
                    fromScene = Scenes.Bouncer,
                    toScene = Scenes.Lockscreen,
                    progress = flowOf(-0.4f),
                    isInitiatedByUserInput = true,
                    isUserInputOngoing = flowOf(true),
                )
            runCurrent()

            assertThat(kosmos.fakeDeviceEntryFaceAuthRepository.isAuthRunning.value).isTrue()
        }

    private fun TestScope.emulateSceneTransition(
        transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
        toScene: SceneKey,
+33 −1
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.DisplayId
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.model.SceneContainerPlugin
@@ -63,6 +64,8 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -96,6 +99,7 @@ constructor(
    private val centralSurfaces: CentralSurfaces,
    private val headsUpInteractor: HeadsUpNotificationInteractor,
    private val occlusionInteractor: SceneContainerOcclusionInteractor,
    private val faceUnlockInteractor: DeviceEntryFaceAuthInteractor,
) : CoreStartable {

    override fun start() {
@@ -108,6 +112,7 @@ constructor(
            respondToFalsingDetections()
            hydrateWindowFocus()
            hydrateInteractionState()
            handleBouncerOverscroll()
        } else {
            sceneLogger.logFrameworkEnabled(
                isEnabled = false,
@@ -227,7 +232,7 @@ constructor(
                            is ObservableTransitionState.Idle -> setOf(transitionState.scene)
                            is ObservableTransitionState.Transition ->
                                setOf(
                                    transitionState.progress,
                                    transitionState.fromScene,
                                    transitionState.toScene,
                                )
                        }
@@ -461,6 +466,33 @@ constructor(
        }
    }

    private fun handleBouncerOverscroll() {
        applicationScope.launch {
            sceneInteractor.transitionState
                // Only consider transitions.
                .filterIsInstance<ObservableTransitionState.Transition>()
                // Only consider user-initiated (e.g. drags) that go from bouncer to lockscreen.
                .filter { transition ->
                    transition.fromScene == Scenes.Bouncer &&
                        transition.toScene == Scenes.Lockscreen &&
                        transition.isInitiatedByUserInput
                }
                .flatMapLatest { it.progress }
                // Figure out the direction of scrolling.
                .map { progress ->
                    when {
                        progress > 0 -> 1
                        progress < 0 -> -1
                        else -> 0
                    }
                }
                .distinctUntilChanged()
                // Only consider negative scrolling, AKA overscroll.
                .filter { it == -1 }
                .collect { faceUnlockInteractor.onSwipeUpOnBouncer() }
        }
    }

    private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) {
        sceneInteractor.changeScene(
            toScene = targetSceneKey,