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

Commit 8205a699 authored by Alejandro Nijamkin's avatar Alejandro Nijamkin
Browse files

[flexiglass] Fixes lockdown

There was an issue that, when the user entered lockdown mode, the device
would clearly be in that mode but the scene didn't change from Gone to
Lockscreen.

This was fixed by making sure that
DeviceUnlockedInteractor.deviceUnlockStatus took lockdown state into
account. This required a medium sized refactor to move the lockdown flow
from DeviceEntryInteractor to DeviceUnlockedInteractor (for background,
see ** below). Once the lockdown flow was moved into
DeviceUnlockedInteractor, it was possible to have its deviceUnlockStatus
take it into account. A device in lockdown has a DeviceUnlockStatus
where isUnlocked=false.

This also means that all downstream consumers of DeviceUnlockedInteractor.deviceUnlockStatus are now getting proper values when the device enters or exits lockdown.

The above fixed the original issue but then a new issue was revealed:
entering the correct PIN after lockdown didn't move bouncer to the Gone
scene.

As it turns out, system_server wasn't actually reporting that the need
for strong auth was removed so the system UI authentication code still
believed that the device isn't locked, leaving the user locked on the
bouncer scene - not good!

The solution was to move the call to LockPatternUtils.userPresent that
was previously a side effect of DeviceEntryInteractor.isDeviceEntered
(added in ag/28135648) to happen inside AuthenticationRepository
instead, right next to where we tell LockPatterUtils of a successful
password attempt.

This is a safe change because the legacy code seems to be doing
something similar, albeit a little different. The legacy code calls
userPresent only when the keyguard is hidden and, if I understand
correctly, hides the keyguard based on the event of a successful
credential check. In Flexiglass, the keyguard isn't hidden based on that
event; instead, it's hidden in response to a state change that tells
Flexiglass that the device has become unlocked. This means that we have
a sort of a catch 22 where we can't call userPresent before leaving the
bouncer but we can't leave the bouncer before calling userPresent.

** DeviceUnlockedInteractor history: The reason DeviceUnlockedInteractor exists as a separate class from
the seemingly better named DeviceEntryInteractor is to avoid a circular
dependency. DeviceEntryInteractor needs to depend on SceneInteractor
because that's how it knows whether the device is entered (whether we
moved to the Gone scene). The SceneInteractor needs to know if the
device unlocked to validate scene transition requests and make sure that
it's okay to move to the Gone scene (can only happen if the device is
unlocked, forbidden otherwise - a security feature).

Fix: 352404539
Test: manually verified that (1) entering lockdown from the power menu
moved to the lockscreen scene and that (2) entering the correct
credentials in the bouncer during lockdown moved to the gone scene
(tried 10 times)
Test: unit tests related to the moved logic were also moved
Test: unit tests added for lockdown in DeviceUnlockedInteractor
Flag: com.android.systemui.scene_container

Change-Id: Iffd4a1737bbeb1e3d953134ec3073c11e350bf9a
parent ae097932
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -36,7 +36,7 @@ import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.bouncer.shared.flag.fakeComposeBouncerFlags
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
@@ -83,7 +83,7 @@ class BouncerMessageViewModelTest : SysuiTestCase() {
        underTest = kosmos.bouncerMessageViewModel
        overrideResource(R.string.kg_trust_agent_disabled, "Trust agent is unavailable")
        kosmos.fakeSystemPropertiesHelper.set(
            DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
            DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
            "not mainline reboot"
        )
    }
+0 −240
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -32,27 +31,14 @@ import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.AdaptiveAuthRequest
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.BouncerLockedOut
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.PolicyLockdown
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.SecurityTimeout
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.TrustAgentDisabled
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UnattendedUpdate
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UserLockdown
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.fakeSystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
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.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
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
@@ -61,7 +47,6 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -438,231 +423,6 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
            assertThat(isUnlocked).isTrue()
        }

    @Test
    fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
        testScope.runTest {
            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
            kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
            runCurrent()

            verifyRestrictionReasonsForAuthFlags(
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to null,
                LockPatternUtils.StrongAuthTracker
                    .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
                    null,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
                    null,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to null
            )
        }

    @Test
    fun deviceEntryRestrictionReason_whenFaceIsEnrolledAndEnabled_mapsToAuthFlagsState() =
        testScope.runTest {
            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
            kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
            kosmos.fakeSystemPropertiesHelper.set(
                DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
                "not mainline reboot"
            )
            runCurrent()

            verifyRestrictionReasonsForAuthFlags(
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
                    DeviceNotUnlockedSinceReboot,
                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
                    AdaptiveAuthRequest,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
                    BouncerLockedOut,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
                    SecurityTimeout,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
                    UserLockdown,
                LockPatternUtils.StrongAuthTracker
                    .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
                    NonStrongBiometricsSecurityTimeout,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
                    UnattendedUpdate,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
                    PolicyLockdown,
                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
                    null,
            )
        }

    @Test
    fun deviceEntryRestrictionReason_whenFingerprintIsEnrolledAndEnabled_mapsToAuthFlagsState() =
        testScope.runTest {
            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
            kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
            kosmos.fakeSystemPropertiesHelper.set(
                DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
                "not mainline reboot"
            )
            runCurrent()

            verifyRestrictionReasonsForAuthFlags(
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
                    DeviceNotUnlockedSinceReboot,
                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
                    AdaptiveAuthRequest,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
                    BouncerLockedOut,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
                    SecurityTimeout,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
                    UserLockdown,
                LockPatternUtils.StrongAuthTracker
                    .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
                    NonStrongBiometricsSecurityTimeout,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
                    UnattendedUpdate,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
                    PolicyLockdown,
                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
                    null,
            )
        }

    @Test
    fun deviceEntryRestrictionReason_whenTrustAgentIsEnabled_mapsToAuthFlagsState() =
        testScope.runTest {
            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
            kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
            kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false)
            kosmos.fakeSystemPropertiesHelper.set(
                DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
                "not mainline reboot"
            )
            runCurrent()

            verifyRestrictionReasonsForAuthFlags(
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
                    DeviceNotUnlockedSinceReboot,
                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
                    AdaptiveAuthRequest,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
                    BouncerLockedOut,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
                    SecurityTimeout,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
                    UserLockdown,
                LockPatternUtils.StrongAuthTracker
                    .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
                    NonStrongBiometricsSecurityTimeout,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
                    UnattendedUpdate,
                LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
                    PolicyLockdown,
                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
                    TrustAgentDisabled,
                LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
                    TrustAgentDisabled,
            )
        }

    @Test
    fun deviceEntryRestrictionReason_whenDeviceRebootedForMainlineUpdate_mapsToTheCorrectReason() =
        testScope.runTest {
            val deviceEntryRestrictionReason by
                collectLastValue(underTest.deviceEntryRestrictionReason)
            kosmos.fakeSystemPropertiesHelper.set(
                DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
                DeviceEntryInteractor.REBOOT_MAINLINE_UPDATE
            )
            kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
                AuthenticationFlags(
                    userId = 1,
                    flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
                )
            )
            runCurrent()

            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
            kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
            runCurrent()

            assertThat(deviceEntryRestrictionReason).isNull()

            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
            runCurrent()

            assertThat(deviceEntryRestrictionReason)
                .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)

            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
            runCurrent()

            assertThat(deviceEntryRestrictionReason)
                .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)

            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
            kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
            runCurrent()

            assertThat(deviceEntryRestrictionReason)
                .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
        }

    @Test
    fun reportUserPresent_whenDeviceEntered() =
        testScope.runTest {
            val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
            assertThat(isDeviceEntered).isFalse()
            assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(0)

            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                SuccessFingerprintAuthenticationStatus(0, true)
            )
            runCurrent()
            switchToScene(Scenes.Gone)
            assertThat(isDeviceEntered).isTrue()
            assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(1)

            switchToScene(Scenes.Lockscreen)
            assertThat(isDeviceEntered).isFalse()
            assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(1)

            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                SuccessFingerprintAuthenticationStatus(0, true)
            )
            switchToScene(Scenes.Gone)
            assertThat(isDeviceEntered).isTrue()
            assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(2)
        }

    private fun TestScope.verifyRestrictionReasonsForAuthFlags(
        vararg authFlagToDeviceEntryRestriction: Pair<Int, DeviceEntryRestrictionReason?>
    ) {
        val deviceEntryRestrictionReason by collectLastValue(underTest.deviceEntryRestrictionReason)

        authFlagToDeviceEntryRestriction.forEach { (flag, expectedReason) ->
            kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
                AuthenticationFlags(userId = 1, flag = flag)
            )
            runCurrent()

            if (expectedReason == null) {
                assertThat(deviceEntryRestrictionReason).isNull()
            } else {
                assertThat(deviceEntryRestrictionReason).isEqualTo(expectedReason)
            }
        }
    }

    private fun switchToScene(sceneKey: SceneKey) {
        sceneInteractor.changeScene(sceneKey, "reason")
    }
+244 −14

File changed.

Preview size limit exceeded, changes collapsed.

+1 −0
Original line number Diff line number Diff line
@@ -288,6 +288,7 @@ constructor(
    override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
        withContext(backgroundDispatcher) {
            if (isSuccessful) {
                lockPatternUtils.userPresent(selectedUserId)
                lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId)
                _hasLockoutOccurred.value = false
            } else {
+5 −5
Original line number Diff line number Diff line
@@ -32,7 +32,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.BiometricMessageInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
import com.android.systemui.deviceentry.shared.model.FaceFailureMessage
import com.android.systemui.deviceentry.shared.model.FaceLockoutMessage
@@ -75,7 +75,7 @@ class BouncerMessageViewModel(
    private val clock: SystemClock,
    private val biometricMessageInteractor: BiometricMessageInteractor,
    private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
    private val deviceEntryInteractor: DeviceEntryInteractor,
    private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
    private val fingerprintInteractor: DeviceEntryFingerprintAuthInteractor,
    flags: ComposeBouncerFlags,
) {
@@ -119,7 +119,7 @@ class BouncerMessageViewModel(
                        }
                    } else if (authMethod.isSecure) {
                        combine(
                            deviceEntryInteractor.deviceEntryRestrictionReason,
                            deviceUnlockedInteractor.deviceEntryRestrictionReason,
                            lockoutMessage,
                            fingerprintInteractor.isFingerprintCurrentlyAllowedOnBouncer,
                            resetToDefault,
@@ -413,7 +413,7 @@ object BouncerMessageViewModelModule {
        clock: SystemClock,
        biometricMessageInteractor: BiometricMessageInteractor,
        faceAuthInteractor: DeviceEntryFaceAuthInteractor,
        deviceEntryInteractor: DeviceEntryInteractor,
        deviceUnlockedInteractor: DeviceUnlockedInteractor,
        fingerprintInteractor: DeviceEntryFingerprintAuthInteractor,
        flags: ComposeBouncerFlags,
        userSwitcherViewModel: UserSwitcherViewModel,
@@ -427,7 +427,7 @@ object BouncerMessageViewModelModule {
            clock = clock,
            biometricMessageInteractor = biometricMessageInteractor,
            faceAuthInteractor = faceAuthInteractor,
            deviceEntryInteractor = deviceEntryInteractor,
            deviceUnlockedInteractor = deviceUnlockedInteractor,
            fingerprintInteractor = fingerprintInteractor,
            flags = flags,
            selectedUser = userSwitcherViewModel.selectedUser,
Loading