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

Commit 3516029b authored by Alejandro Nijamkin's avatar Alejandro Nijamkin
Browse files

[flexiglass] Enables fingerprint unlock regardless of AOD setting

In ag/29467317, we added logic to allow fingerprint unlock to unlock the
device while AOD is on. It was an oversight that we ignored the case of
when AOD is off. In fact, it shouldn't matter if the device is asleep in
AOD or not, a fingerprint unlock should wake up and unlock the device
regardless. This CL achieves that.

Fix: 370795290
Test: manually verified that a SFPS fingerprint unlock properly moves to
the Gone scene while the device is in AOD or just sleeping - tried with
the AOD setting toggled on and toggled off.
Flag: com.android.systemui.scene_container

Change-Id: Ib83df9c5264abb6dc723907d99f50dd7b40e915c
parent 3061256e
Loading
Loading
Loading
Loading
+1 −29
Original line number Diff line number Diff line
@@ -31,11 +31,8 @@ import com.android.systemui.flags.fakeSystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -243,16 +240,11 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
        }

    @Test
    fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleepInAod() =
    fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleep() =
        testScope.runTest {
            val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()

            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
                from = KeyguardState.LOCKSCREEN,
                to = KeyguardState.AOD,
                testScope = this,
            )
            kosmos.powerInteractor.setAsleepForTest()
            runCurrent()

@@ -265,26 +257,6 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
            assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
        }

    @Test
    fun deviceUnlockStatus_staysLocked_whenFingerprintUnlocked_whileDeviceAsleep() =
        testScope.runTest {
            val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
            assertThat(kosmos.keyguardTransitionInteractor.getCurrentState())
                .isEqualTo(KeyguardState.LOCKSCREEN)

            kosmos.powerInteractor.setAsleepForTest()
            runCurrent()

            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()

            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                SuccessFingerprintAuthenticationStatus(0, true)
            )
            runCurrent()
            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
        }

    @Test
    fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
        testScope.runTest {
+1 −1
Original line number Diff line number Diff line
@@ -753,7 +753,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
                lastSleepReason = WakeSleepReason.POWER_BUTTON,
                powerButtonLaunchGestureTriggered = false,
            )
            transitionStateFlow.value = Transition(from = Scenes.Shade, to = Scenes.Lockscreen)
            transitionStateFlow.value = Transition(from = Scenes.Gone, to = Scenes.Lockscreen)
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)

            kosmos.fakePowerRepository.updateWakefulness(
+12 −0
Original line number Diff line number Diff line
@@ -18,13 +18,18 @@ package com.android.systemui.deviceentry

import com.android.keyguard.EmptyLockIconViewController
import com.android.keyguard.LockIconViewController
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule
import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import dagger.Binds
import dagger.Lazy
import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import dagger.multibindings.Multibinds

@Module(includes = [DeviceEntryRepositoryModule::class, FaceWakeUpTriggersConfigModule::class])
@@ -34,6 +39,13 @@ abstract class DeviceEntryModule {
     */
    @Multibinds abstract fun deviceEntryIconTransitionSet(): Set<DeviceEntryIconTransition>

    @Binds
    @IntoMap
    @ClassKey(DeviceUnlockedInteractor.Activator::class)
    abstract fun deviceUnlockedInteractorActivator(
        activator: DeviceUnlockedInteractor.Activator
    ): CoreStartable

    companion object {
        @Provides
        @SysUISingleton
+6 −0
Original line number Diff line number Diff line
@@ -5,6 +5,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.Background
import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.repository.UserRepository
import dagger.Binds
@@ -42,6 +43,8 @@ interface DeviceEntryRepository {
     */
    val isBypassEnabled: StateFlow<Boolean>

    val deviceUnlockStatus: MutableStateFlow<DeviceUnlockStatus>

    /**
     * Whether the lockscreen is enabled for the current user. This is `true` whenever the user has
     * chosen any secure authentication method and even if they set the lockscreen to be dismissed
@@ -84,6 +87,9 @@ constructor(
                initialValue = keyguardBypassController.bypassEnabled,
            )

    override val deviceUnlockStatus =
        MutableStateFlow(DeviceUnlockStatus(isUnlocked = false, deviceUnlockSource = null))

    override suspend fun isLockscreenEnabled(): Boolean {
        return withContext(backgroundDispatcher) {
            val selectedUserId = userRepository.getSelectedUserInfo().id
+74 −45
Original line number Diff line number Diff line
@@ -16,7 +16,9 @@

package com.android.systemui.deviceentry.domain.interactor

import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.systemui.CoreStartable
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.dagger.SysUISingleton
@@ -26,42 +28,40 @@ import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReaso
import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.TrustInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class DeviceUnlockedInteractor
@Inject
constructor(
    @Application private val applicationScope: CoroutineScope,
    authenticationInteractor: AuthenticationInteractor,
    deviceEntryRepository: DeviceEntryRepository,
    private val authenticationInteractor: AuthenticationInteractor,
    private val repository: DeviceEntryRepository,
    trustInteractor: TrustInteractor,
    faceAuthInteractor: DeviceEntryFaceAuthInteractor,
    fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
    private val powerInteractor: PowerInteractor,
    private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
    private val systemPropertiesHelper: SystemPropertiesHelper,
    keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
) : ExclusiveActivatable() {

    private val deviceUnlockSource =
        merge(
@@ -69,7 +69,7 @@ constructor(
            faceAuthInteractor.isAuthenticated
                .filter { it }
                .map {
                    if (deviceEntryRepository.isBypassEnabled.value) {
                    if (repository.isBypassEnabled.value) {
                        DeviceUnlockSource.FaceWithBypass
                    } else {
                        DeviceUnlockSource.FaceWithoutBypass
@@ -163,43 +163,59 @@ constructor(
     * proceed.
     */
    val deviceUnlockStatus: StateFlow<DeviceUnlockStatus> =
        authenticationInteractor.authenticationMethod
            .flatMapLatest { authMethod ->
        repository.deviceUnlockStatus.asStateFlow()

    override suspend fun onActivated(): Nothing {
        authenticationInteractor.authenticationMethod.collectLatest { authMethod ->
            if (!authMethod.isSecure) {
                    flowOf(DeviceUnlockStatus(true, null))
                // Device remains unlocked as long as the authentication method is not secure.
                Log.d(TAG, "remaining unlocked because auth method not secure")
                repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, null)
            } else if (authMethod == AuthenticationMethodModel.Sim) {
                    // Device is locked if SIM is locked.
                    flowOf(DeviceUnlockStatus(false, null))
                // Device remains locked while SIM is locked.
                Log.d(TAG, "remaining locked because SIM locked")
                repository.deviceUnlockStatus.value = DeviceUnlockStatus(false, null)
            } else {
                    combine(
                            powerInteractor.isAsleep,
                            isInLockdown,
                            keyguardTransitionInteractor
                                .transitionValue(KeyguardState.AOD)
                                .map { it == 1f }
                                .distinctUntilChanged(),
                            ::Triple,
                try {
                    Log.d(TAG, "started watching for lock and unlock events")
                    coroutineScope {
                        launch {
                            // Unlock the device when a new unlock source is detected.
                            deviceUnlockSource.collect {
                                Log.d(TAG, "unlocking due to \"$it\"")
                                repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, it)
                            }
                        }

                        launch {
                            // Lock events.
                            merge(
                                    // Device goes to sleep.
                                    powerInteractor.isAsleep
                                        .distinctUntilChanged()
                                        .filter { it }
                                        .map { "asleep" },
                                    // Device enters lockdown.
                                    isInLockdown
                                        .distinctUntilChanged()
                                        .filter { it }
                                        .map { "lockdown" },
                                )
                        .flatMapLatestConflated { (isAsleep, isInLockdown, isAod) ->
                            val isForceLocked =
                                when {
                                    isAsleep && !isAod -> true
                                    isInLockdown -> true
                                    else -> false
                                .collect { reason: String ->
                                    Log.d(TAG, "locking due to \"$reason\"")
                                    repository.deviceUnlockStatus.value =
                                        DeviceUnlockStatus(false, null)
                                }
                            if (isForceLocked) {
                                flowOf(DeviceUnlockStatus(false, null))
                            } else {
                                deviceUnlockSource.map { DeviceUnlockStatus(true, it) }
                        }
                    }
                } finally {
                    Log.d(TAG, "stopped watching for lock and unlock events")
                }
            }
        }
            .stateIn(
                scope = applicationScope,
                started = SharingStarted.Eagerly,
                initialValue = DeviceUnlockStatus(false, null),
            )

        awaitCancellation()
    }

    private fun DeviceEntryRestrictionReason?.isInLockdown(): Boolean {
        return when (this) {
@@ -226,7 +242,20 @@ constructor(
        return systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
    }

    /** [CoreStartable] that activates the [DeviceUnlockedInteractor]. */
    class Activator
    @Inject
    constructor(
        @Application private val applicationScope: CoroutineScope,
        private val interactor: DeviceUnlockedInteractor,
    ) : CoreStartable {
        override fun start() {
            applicationScope.launch { interactor.activate() }
        }
    }

    companion object {
        private val TAG = "DeviceUnlockedInteractor"
        @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
        @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
    }
Loading