diff options
| author | 2024-02-27 08:15:33 +0000 | |
|---|---|---|
| committer | 2024-03-12 02:54:59 +0000 | |
| commit | ba160eb4075cccbdb45ed565fbafd62ad5f8902e (patch) | |
| tree | 6b856afd3bed0be4c276256c3140b9559468a077 | |
| parent | 7e73502a740f06c10f1efc6c0dc85896009c8965 (diff) | |
[flexiglass] Add device entry restriction reason to DeviceEntryInteractor
Whenever certain device entry authentication methods are restricted, this field will specify the reason so the view layer can display a specific message to the user.
Bug: 299343534
Flag: ACONFIG com.android.systemui.compose_bouncer DEVELOPMENT
Test: atest DeviceEntryInteractorTest
Change-Id: If71980e682bc151f147117bd1abad8ef50bf27fb
13 files changed, 522 insertions, 25 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt index 4f44705b7e72..70ceb2a75d7c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.deviceentry.domain.interactor 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 @@ -27,8 +28,21 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode 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.fakeSystemPropertiesHelper +import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.fakeTrustRepository +import com.android.systemui.keyguard.shared.model.AuthenticationFlags import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags @@ -36,6 +50,7 @@ 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 @@ -230,8 +245,8 @@ class DeviceEntryInteractorTest : SysuiTestCase() { assertThat(canSwipeToEnter).isFalse() trustRepository.setCurrentUserTrusted(true) - runCurrent() faceAuthRepository.isAuthenticated.value = false + runCurrent() assertThat(canSwipeToEnter).isTrue() } @@ -383,6 +398,204 @@ 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) + } + + 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") } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt index d3fa3603d722..cd79ed1a8965 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt @@ -54,7 +54,7 @@ class ShadeControllerSceneImplTest : SysuiTestCase() { private val kosmos = Kosmos() private val testScope = kosmos.testScope private val sceneInteractor = kosmos.sceneInteractor - private val deviceEntryInteractor = kosmos.deviceEntryInteractor + private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor } private lateinit var shadeInteractor: ShadeInteractor private lateinit var underTest: ShadeControllerSceneImpl diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt index 3092defc8b0e..7f6fc914e92b 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt @@ -177,7 +177,7 @@ constructor( ) .toMessage() } else if ( - trustOrBiometricsAvailable && flags.primaryAuthRequiredForUnattendedUpdate + trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredForUnattendedUpdate ) { BouncerMessageStrings.authRequiredForUnattendedUpdate( currentSecurityMode.toAuthModel() diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt index 029a4f33cd27..fa2421a3516d 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt @@ -16,24 +16,30 @@ package com.android.systemui.deviceentry.domain.interactor +import androidx.annotation.VisibleForTesting import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository -import com.android.systemui.keyguard.data.repository.TrustRepository +import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason +import com.android.systemui.flags.SystemPropertiesHelper +import com.android.systemui.keyguard.domain.interactor.TrustInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.util.kotlin.Quad import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine 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.onStart @@ -55,10 +61,13 @@ constructor( private val repository: DeviceEntryRepository, private val authenticationInteractor: AuthenticationInteractor, private val sceneInteractor: SceneInteractor, - deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository, - trustRepository: TrustRepository, + faceAuthInteractor: DeviceEntryFaceAuthInteractor, + private val fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, + private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor, + private val trustInteractor: TrustInteractor, flags: SceneContainerFlags, deviceUnlockedInteractor: DeviceUnlockedInteractor, + private val systemPropertiesHelper: SystemPropertiesHelper, ) { /** * Whether the device is unlocked. @@ -96,8 +105,8 @@ constructor( */ private val isPassivelyAuthenticated = merge( - trustRepository.isCurrentUserTrusted, - deviceEntryFaceAuthRepository.isAuthenticated, + trustInteractor.isTrusted, + faceAuthInteractor.authenticated, ) .onStart { emit(false) } @@ -134,6 +143,67 @@ constructor( initialValue = null, ) + private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled + private val fingerprintEnrolledAndEnabled = + biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled + private val trustAgentEnabled = trustInteractor.isEnrolledAndEnabled + + private val faceOrFingerprintOrTrustEnabled: Flow<Triple<Boolean, Boolean, Boolean>> = + combine(faceEnrolledAndEnabled, fingerprintEnrolledAndEnabled, trustAgentEnabled, ::Triple) + + /** + * Reason why device entry is restricted to certain authentication methods for the current user. + * + * Emits null when there are no device entry restrictions active. + */ + val deviceEntryRestrictionReason: Flow<DeviceEntryRestrictionReason?> = + faceOrFingerprintOrTrustEnabled.flatMapLatest { + (faceEnabled, fingerprintEnabled, trustEnabled) -> + if (faceEnabled || fingerprintEnabled || trustEnabled) { + combine( + biometricSettingsInteractor.authenticationFlags, + faceAuthInteractor.lockedOut, + fingerprintAuthInteractor.isLockedOut, + trustInteractor.isTrustAgentCurrentlyAllowed, + ::Quad + ) + .map { (authFlags, isFaceLockedOut, isFingerprintLockedOut, trustManaged) -> + when { + authFlags.isPrimaryAuthRequiredAfterReboot && + wasRebootedForMainlineUpdate -> + DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate + authFlags.isPrimaryAuthRequiredAfterReboot -> + DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot + authFlags.isPrimaryAuthRequiredAfterDpmLockdown -> + DeviceEntryRestrictionReason.PolicyLockdown + authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown + authFlags.isPrimaryAuthRequiredForUnattendedUpdate -> + DeviceEntryRestrictionReason.UnattendedUpdate + authFlags.isPrimaryAuthRequiredAfterTimeout -> + DeviceEntryRestrictionReason.SecurityTimeout + authFlags.isPrimaryAuthRequiredAfterLockout -> + DeviceEntryRestrictionReason.BouncerLockedOut + isFingerprintLockedOut -> + DeviceEntryRestrictionReason.StrongBiometricsLockedOut + isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() -> + DeviceEntryRestrictionReason.StrongBiometricsLockedOut + isFaceLockedOut -> DeviceEntryRestrictionReason.NonStrongFaceLockedOut + authFlags.isSomeAuthRequiredAfterAdaptiveAuthRequest -> + DeviceEntryRestrictionReason.AdaptiveAuthRequest + (trustEnabled && !trustManaged) && + (authFlags.someAuthRequiredAfterTrustAgentExpired || + authFlags.someAuthRequiredAfterUserRequest) -> + DeviceEntryRestrictionReason.TrustAgentDisabled + authFlags.strongerAuthRequiredAfterNonStrongBiometricsTimeout -> + DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout + else -> null + } + } + } else { + flowOf(null) + } + } + /** * Attempt to enter the device and dismiss the lockscreen. If authentication is required to * unlock the device it will transition to bouncer. @@ -187,4 +257,12 @@ constructor( } } } + + private val wasRebootedForMainlineUpdate + get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE + + companion object { + @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last" + @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update" + } } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt new file mode 100644 index 000000000000..5b672ac372db --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt @@ -0,0 +1,119 @@ +/* + * 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.deviceentry.shared.model + +/** List of reasons why device entry can be restricted to certain authentication methods. */ +enum class DeviceEntryRestrictionReason { + /** + * Reason: Lockdown initiated by the user. + * + * Restriction: Only bouncer based device entry is allowed. + */ + UserLockdown, + + /** + * Reason: Not unlocked since reboot. + * + * Restriction: Only bouncer based device entry is allowed. + */ + DeviceNotUnlockedSinceReboot, + + /** + * Reason: Not unlocked since reboot after a mainline update. + * + * Restriction: Only bouncer based device entry is allowed. + */ + DeviceNotUnlockedSinceMainlineUpdate, + + /** + * Reason: Lockdown initiated by admin through installed device policy + * + * Restriction: Only bouncer based device entry is allowed. + */ + PolicyLockdown, + + /** + * Reason: Device entry credentials need to be used for an unattended update at a later point in + * time. + * + * Restriction: Only bouncer based device entry is allowed. + */ + UnattendedUpdate, + + /** + * Reason: Device was not unlocked using PIN/Pattern/Password for a prolonged period of time. + * + * Restriction: Only bouncer based device entry is allowed. + */ + SecurityTimeout, + + /** + * Reason: A "class 3"/strong biometrics device entry method was locked out after many incorrect + * authentication attempts. + * + * Restriction: Only bouncer based device entry is allowed. + * + * @see + * [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes) + */ + StrongBiometricsLockedOut, + + /** + * Reason: A weak (class 2)/convenience (class 3) strength face biometrics device entry method + * was locked out after many incorrect authentication attempts. + * + * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed. + * + * @see + * [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes) + */ + NonStrongFaceLockedOut, + + /** + * Reason: Device was last unlocked using a weak/convenience strength biometrics device entry + * method and a stronger authentication method wasn't used to unlock the device for a prolonged + * period of time. + * + * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed. + * + * @see + * [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes) + */ + NonStrongBiometricsSecurityTimeout, + + /** + * Reason: A trust agent that was granting trust has either expired or disabled by the user by + * opening the power menu. + * + * Restriction: Only non trust agent device entry methods are allowed. + */ + TrustAgentDisabled, + + /** + * Reason: Theft protection is enabled after too many unlock attempts. + * + * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed. + */ + AdaptiveAuthRequest, + + /** + * Reason: Bouncer was locked out after too many incorrect authentication attempts. + * + * Restriction: Only bouncer based device entry is allowed. + */ + BouncerLockedOut, +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt index 6fa20de1fb7f..1e3c6049edd4 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt @@ -20,36 +20,34 @@ import android.os.SystemProperties import javax.inject.Inject import javax.inject.Singleton -/** - * Proxy to make {@link SystemProperties} easily testable. - */ +/** Proxy to make {@link SystemProperties} easily testable. */ @Singleton open class SystemPropertiesHelper @Inject constructor() { - fun get(name: String): String { + open fun get(name: String): String { return SystemProperties.get(name) } - fun get(name: String, def: String?): String { + open fun get(name: String, def: String?): String { return SystemProperties.get(name, def) } - fun getBoolean(name: String, default: Boolean): Boolean { + open fun getBoolean(name: String, default: Boolean): Boolean { return SystemProperties.getBoolean(name, default) } - fun setBoolean(name: String, value: Boolean) { + open fun setBoolean(name: String, value: Boolean) { SystemProperties.set(name, if (value) "1" else "0") } - fun set(name: String, value: String) { + open fun set(name: String, value: String) { SystemProperties.set(name, value) } - fun set(name: String, value: Int) { + open fun set(name: String, value: Int) { set(name, value.toString()) } - fun erase(name: String) { + open fun erase(name: String) { set(name, "") } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt index 08904b6ffa86..d6f3634fdcd5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt @@ -32,6 +32,9 @@ data class AuthenticationFlags(val userId: Int, val flag: Int) { val isPrimaryAuthRequiredAfterTimeout = containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT) + val isPrimaryAuthRequiredAfterLockout = + containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) + val isPrimaryAuthRequiredAfterDpmLockdown = containsFlag( flag, @@ -47,7 +50,7 @@ data class AuthenticationFlags(val userId: Int, val flag: Int) { LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED ) - val primaryAuthRequiredForUnattendedUpdate = + val isPrimaryAuthRequiredForUnattendedUpdate = containsFlag( flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt index 6dd8d07b356b..0660d0095c7d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt @@ -18,6 +18,7 @@ package com.android.systemui import com.android.systemui.classifier.FakeClassifierModule import com.android.systemui.data.FakeSystemUiDataLayerModule import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.FakeSystemPropertiesHelperModule import com.android.systemui.log.FakeUiEventLoggerModule import com.android.systemui.settings.FakeSettingsModule import com.android.systemui.statusbar.policy.FakeConfigurationControllerModule @@ -33,6 +34,7 @@ import dagger.Module FakeConfigurationControllerModule::class, FakeExecutorModule::class, FakeFeatureFlagsClassicModule::class, + FakeSystemPropertiesHelperModule::class, FakeSettingsModule::class, FakeSplitShadeStateControllerModule::class, FakeSystemClockModule::class, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt index 69b769eb2321..bc0bf9dd069f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt @@ -27,6 +27,9 @@ import com.android.systemui.coroutines.collectValues import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor +import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor import com.android.systemui.scene.SceneContainerFrameworkModule import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.SceneDataSource @@ -56,6 +59,7 @@ import kotlinx.coroutines.test.runTest CoroutineTestScopeModule::class, FakeSystemUiModule::class, SceneContainerFrameworkModule::class, + FaceWakeUpTriggersConfigModule::class, ] ) interface SysUITestModule { @@ -69,6 +73,11 @@ interface SysUITestModule { @Binds @SysUISingleton fun bindsShadeInteractor(sii: ShadeInteractorImpl): ShadeInteractor @Binds fun bindSceneDataSource(delegator: SceneDataSourceDelegator): SceneDataSource + @Binds + fun provideFaceAuthInteractor( + sysUIFaceAuthInteractor: SystemUIDeviceEntryFaceAuthInteractor + ): DeviceEntryFaceAuthInteractor + companion object { @Provides fun provideSysuiTestableContext(test: SysuiTestCase): SysuiTestableContext = test.context diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt index 62a1aa93f35a..3d84291292c9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt @@ -17,6 +17,7 @@ package com.android.systemui import android.app.ActivityManager import android.app.admin.DevicePolicyManager +import android.app.trust.TrustManager import android.os.UserManager import android.service.notification.NotificationListenerService import android.util.DisplayMetrics @@ -27,6 +28,7 @@ import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardViewController import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.demomode.DemoModeController import com.android.systemui.dump.DumpManager @@ -36,6 +38,7 @@ import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.log.LogBuffer import com.android.systemui.log.dagger.BiometricLog import com.android.systemui.log.dagger.BroadcastDispatcherLog +import com.android.systemui.log.dagger.FaceAuthLog import com.android.systemui.log.dagger.SceneFrameworkLog import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager import com.android.systemui.model.SysUiState @@ -65,10 +68,12 @@ import com.android.systemui.statusbar.phone.ScrimController import com.android.systemui.statusbar.phone.SystemUIDialogManager import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.ZenModeController import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.util.mockito.mock +import com.android.systemui.util.settings.GlobalSettings import com.android.wm.shell.bubbles.Bubbles import dagger.Binds import dagger.Module @@ -123,6 +128,10 @@ data class TestMocksModule( @get:Provides val deviceEntryIconTransitions: Set<DeviceEntryIconTransition> = emptySet(), @get:Provides val communalInteractor: CommunalInteractor = mock(), @get:Provides val sceneLogger: SceneLogger = mock(), + @get:Provides val trustManager: TrustManager = mock(), + @get:Provides val primaryBouncerInteractor: PrimaryBouncerInteractor = mock(), + @get:Provides val keyguardStateController: KeyguardStateController = mock(), + @get:Provides val globalSettings: GlobalSettings = mock(), // log buffers @get:[Provides BroadcastDispatcherLog] @@ -131,6 +140,8 @@ data class TestMocksModule( val sceneLogBuffer: LogBuffer = mock(), @get:[Provides BiometricLog] val biometricLogger: LogBuffer = mock(), + @get:[Provides FaceAuthLog] + val faceAuthLogger: LogBuffer = mock(), @get:Provides val lsShadeTransitionLogger: LSShadeTransitionLogger = mock(), // framework mocks diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt index 0d1a31f9605e..e73e2950bbb9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt @@ -18,8 +18,8 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.deviceentry.data.repository.deviceEntryRepository -import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository -import com.android.systemui.keyguard.data.repository.trustRepository +import com.android.systemui.flags.fakeSystemPropertiesHelper +import com.android.systemui.keyguard.domain.interactor.trustInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.scene.domain.interactor.sceneInteractor @@ -34,9 +34,12 @@ val Kosmos.deviceEntryInteractor by repository = deviceEntryRepository, authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, - deviceEntryFaceAuthRepository = deviceEntryFaceAuthRepository, - trustRepository = trustRepository, + faceAuthInteractor = deviceEntryFaceAuthInteractor, + trustInteractor = trustInteractor, flags = sceneContainerFlags, deviceUnlockedInteractor = deviceUnlockedInteractor, + fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor, + biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor, + systemPropertiesHelper = fakeSystemPropertiesHelper, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt new file mode 100644 index 000000000000..2f30d3431a1e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt @@ -0,0 +1,60 @@ +/* + * 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.flags + +import com.android.systemui.dagger.SysUISingleton +import dagger.Binds +import dagger.Module +import javax.inject.Inject + +@SysUISingleton +class FakeSystemPropertiesHelper @Inject constructor() : SystemPropertiesHelper() { + private val fakeProperties = mutableMapOf<String, Any>() + + override fun get(name: String): String { + return fakeProperties[name] as String + } + + override fun get(name: String, def: String?): String { + return checkNotNull(fakeProperties[name] as String? ?: def) + } + + override fun getBoolean(name: String, default: Boolean): Boolean { + return fakeProperties[name] as Boolean? ?: default + } + + override fun setBoolean(name: String, value: Boolean) { + fakeProperties[name] = value + } + + override fun set(name: String, value: String) { + fakeProperties[name] = value + } + + override fun set(name: String, value: Int) { + fakeProperties[name] = value + } + + override fun erase(name: String) { + fakeProperties.remove(name) + } +} + +@Module +interface FakeSystemPropertiesHelperModule { + @Binds fun bindFake(fake: FakeSystemPropertiesHelper): SystemPropertiesHelper +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt index 365d97f3ac15..d6f2f77ca67a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt @@ -62,6 +62,7 @@ val Kosmos.featureFlagsClassicRelease by } val Kosmos.systemPropertiesHelper by Kosmos.Fixture { SystemPropertiesHelper() } +val Kosmos.fakeSystemPropertiesHelper by Kosmos.Fixture { FakeSystemPropertiesHelper() } var Kosmos.serverFlagReader: ServerFlagReader by Kosmos.Fixture { serverFlagReaderFake } val Kosmos.serverFlagReaderFake by Kosmos.Fixture { ServerFlagReaderFake() } var Kosmos.restarter: Restarter by Kosmos.Fixture { mock() } |