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

Commit eca968fd authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[flexiglass] Enables fingerprint unlock regardless of AOD setting" into main

parents 994174a9 3516029b
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