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

Commit 7dfc5cc4 authored by Grace Cheng's avatar Grace Cheng
Browse files

Update secure lock device repository/interactor for secure lock device flags

Update BiometricSettingsRepository and secure lock device repository and
interactor to consider secure lock device
primary auth and biometric auth flags in flows determining allowed
biometric state, including isFingerprintAuthCurrentlyAllowed,
isFaceAuthCurrentlyAllowed, etc. Also adds new flows for determining
secure lock device progress based on strong auth flag state

Bug: 401645997
Bug: 398058587
Bug: 396641431
Flag: android.security.secure_lock_device
Test: atest SecureLockDeviceInteractorTest
Test: atest BiometricSettingsRepositoryTest
Change-Id: Ib853ba67823c9cc7a934e337cef83f1010de5270
parent 319b8a5a
Loading
Loading
Loading
Loading
+111 −7
Original line number Original line Diff line number Diff line
@@ -28,14 +28,17 @@ import android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT
import android.hardware.biometrics.BiometricManager
import android.hardware.biometrics.BiometricManager
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.EnableFlags
import android.security.Flags.FLAG_SECURE_LOCK_DEVICE
import android.testing.TestableLooper
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
@@ -123,7 +126,10 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
        fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
        fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
    }
    }


    private suspend fun createBiometricSettingsRepository() {
    private suspend fun createBiometricSettingsRepository(
        isFingerprintEnrolled: Boolean = true,
        isFaceEnrolled: Boolean = false,
    ) {
        userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
        userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
        userRepository.setSelectedUserInfo(PRIMARY_USER)
        userRepository.setSelectedUserInfo(PRIMARY_USER)
        underTest =
        underTest =
@@ -144,12 +150,15 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
                mobileConnectionsRepository = mobileConnectionsRepository,
                mobileConnectionsRepository = mobileConnectionsRepository,
            )
            )
        testScope.runCurrent()
        testScope.runCurrent()
        fingerprintPropertyRepository.setProperties(
        if (isFingerprintEnrolled) {
            1,
            fingerprintPropertyRepository.supportsUdfps()
            SensorStrength.STRONG,
        }
            FingerprintSensorType.UDFPS_OPTICAL,
        if (isFaceEnrolled) {
            emptyMap(),
            facePropertyRepository.setSensorInfo(
                FaceSensorInfo(id = 0, strength = SensorStrength.STRONG)
            )
            )
        }

        verify(lockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture())
        verify(lockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture())
        verify(authController, times(2)).addCallback(authControllerCallback.capture())
        verify(authController, times(2)).addCallback(authControllerCallback.capture())
    }
    }
@@ -738,6 +747,101 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
            assertThat(isUserInLockdown()).isTrue()
            assertThat(isUserInLockdown()).isTrue()
        }
        }


    @Test
    @EnableFlags(FLAG_SECURE_LOCK_DEVICE)
    fun isFaceAuthCurrentlyAllowed_updatesForSecureLockDeviceFlags() =
        testScope.runTest {
            createBiometricSettingsRepository(isFaceEnrolled = true, isFingerprintEnrolled = false)
            biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FACE)
            whenever(authController.isFaceAuthEnrolled(anyInt())).thenReturn(true)
            enrollmentChange(FACE, PRIMARY_USER_ID, true)
            runCurrent()

            val isFaceAuthCurrentlyAllowed = collectLastValue(underTest.isFaceAuthCurrentlyAllowed)

            // Secure lock device enabled: both flags set
            onStrongAuthChanged(
                PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE or
                    STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE,
                PRIMARY_USER_ID,
            )
            assertThat(isFaceAuthCurrentlyAllowed()).isFalse()

            // Secure lock device primary bouncer auth complete:
            // PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE unset
            onStrongAuthChanged(
                STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE,
                PRIMARY_USER_ID,
            )

            assertThat(isFaceAuthCurrentlyAllowed()).isTrue()
        }

    @Test
    @EnableFlags(FLAG_SECURE_LOCK_DEVICE)
    fun isFingerprintAuthCurrentlyAllowed_updatesForSecureLockDeviceFlags() =
        testScope.runTest {
            createBiometricSettingsRepository(isFaceEnrolled = false, isFingerprintEnrolled = true)
            biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FINGERPRINT)
            whenever(authController.isFingerprintEnrolled(anyInt())).thenReturn(true)
            enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
            runCurrent()

            val isFingerprintAuthCurrentlyAllowed =
                collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)

            // Secure lock device enabled: both flags set
            onStrongAuthChanged(
                PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE or
                    STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE,
                PRIMARY_USER_ID,
            )
            assertThat(isFingerprintAuthCurrentlyAllowed()).isFalse()

            // Secure lock device primary bouncer auth complete:
            // PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE unset
            onStrongAuthChanged(
                STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE,
                PRIMARY_USER_ID,
            )

            assertThat(isFingerprintAuthCurrentlyAllowed()).isTrue()
        }

    @Test
    @EnableFlags(FLAG_SECURE_LOCK_DEVICE)
    fun secureLockDeviceStateUpdates_acrossTwoFactorAuthentication() =
        testScope.runTest {
            createBiometricSettingsRepository()

            val requiresPrimaryAuthForSecureLockDevice =
                collectLastValue(underTest.requiresPrimaryAuthForSecureLockDevice)
            val requiresStrongBiometricAuthForSecureLockDevice =
                collectLastValue(underTest.requiresStrongBiometricAuthForSecureLockDevice)

            // has default value.
            assertThat(requiresPrimaryAuthForSecureLockDevice()).isFalse()

            // enable both secure lock device strong auth flags
            val inSecureLockDevice =
                PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE or
                    STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE
            onStrongAuthChanged(inSecureLockDevice, PRIMARY_USER_ID)

            assertThat(requiresPrimaryAuthForSecureLockDevice()).isTrue()
            assertThat(requiresStrongBiometricAuthForSecureLockDevice()).isFalse()

            // mock secure lock device bouncer auth complete,
            // PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE unset
            onStrongAuthChanged(
                STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE,
                PRIMARY_USER_ID,
            )

            assertThat(requiresPrimaryAuthForSecureLockDevice()).isFalse()
            assertThat(requiresStrongBiometricAuthForSecureLockDevice()).isTrue()
        }

    @Test
    @Test
    fun authFlagChangesForCurrentUserArePropagated() =
    fun authFlagChangesForCurrentUserArePropagated() =
        testScope.runTest {
        testScope.runTest {
+77 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2025 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.securelockdevice.domain.interactor

import android.platform.test.annotations.EnableFlags
import android.security.Flags.FLAG_SECURE_LOCK_DEVICE
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.securelockdevice.data.repository.fakeSecureLockDeviceRepository
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule

@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableFlags(FLAG_SECURE_LOCK_DEVICE)
class SecureLockDeviceInteractorTest : SysuiTestCase() {
    @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()

    private val kosmos = testKosmos()
    private val underTest: SecureLockDeviceInteractor = kosmos.secureLockDeviceInteractor

    @Test
    fun secureLockDeviceStateUpdates_acrossAuthenticationProgress() =
        kosmos.testScope.runTest {
            val isSecureLockDeviceEnabled by collectLastValue(underTest.isSecureLockDeviceEnabled)
            val requiresPrimaryAuthForSecureLockDevice by
                collectLastValue(underTest.requiresPrimaryAuthForSecureLockDevice)
            val requiresStrongBiometricAuthForSecureLockDevice by
                collectLastValue(underTest.requiresStrongBiometricAuthForSecureLockDevice)
            runCurrent()

            kosmos.fakeSecureLockDeviceRepository.onSecureLockDeviceEnabled()
            runCurrent()

            assertThat(isSecureLockDeviceEnabled).isEqualTo(true)
            assertThat(requiresPrimaryAuthForSecureLockDevice).isEqualTo(true)
            assertThat(requiresStrongBiometricAuthForSecureLockDevice).isEqualTo(false)

            kosmos.fakeSecureLockDeviceRepository.onSuccessfulPrimaryAuth()
            runCurrent()

            assertThat(isSecureLockDeviceEnabled).isEqualTo(true)
            assertThat(requiresPrimaryAuthForSecureLockDevice).isEqualTo(false)
            assertThat(requiresStrongBiometricAuthForSecureLockDevice).isEqualTo(true)

            kosmos.fakeSecureLockDeviceRepository.onSecureLockDeviceDisabled()
            runCurrent()

            assertThat(isSecureLockDeviceEnabled).isEqualTo(false)
            assertThat(requiresPrimaryAuthForSecureLockDevice).isEqualTo(false)
            assertThat(requiresStrongBiometricAuthForSecureLockDevice).isEqualTo(false)
        }
}
+48 −3
Original line number Original line Diff line number Diff line
@@ -26,6 +26,7 @@ import android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE
import android.hardware.biometrics.BiometricManager
import android.hardware.biometrics.BiometricManager
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
import android.os.UserHandle
import android.os.UserHandle
import android.security.Flags.secureLockDevice
import android.util.Log
import android.util.Log
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.Dumpable
import com.android.systemui.Dumpable
@@ -115,6 +116,32 @@ interface BiometricSettingsRepository {
     */
     */
    val isCurrentUserInLockdown: Flow<Boolean>
    val isCurrentUserInLockdown: Flow<Boolean>


    /**
     * Primary authentication on the bouncer is required as the first factor of Secure Lock Device
     * authentication, with both strong auth flags {@link
     * PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE} and {@link
     * STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE} set and all biometrics disabled.
     *
     * False when FLAG_SECURE_LOCK_DEVICE is disabled.
     */
    val requiresPrimaryAuthForSecureLockDevice: Flow<Boolean>

    /**
     * Strong biometric-only authentication is requested following a successful primary
     * authentication on the bouncer, in order to complete the two-step authentication process for
     * device entry when secure lock device is enabled.
     *
     * During this step, only the {@link STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE}
     * strong auth flag is set, and primary authentication and non strong biometric authentication
     * are disabled.
     *
     * Becomes false upon successful device entry or exit from the biometric auth screen without
     * authentication (screen off, biometric lockout, etc).
     *
     * False when FLAG_SECURE_LOCK_DEVICE is disabled.
     */
    val requiresStrongBiometricAuthForSecureLockDevice: Flow<Boolean>

    /** Authentication flags set for the current user. */
    /** Authentication flags set for the current user. */
    val authenticationFlags: Flow<AuthenticationFlags>
    val authenticationFlags: Flow<AuthenticationFlags>
}
}
@@ -149,12 +176,30 @@ constructor(


    private val strongAuthTracker = StrongAuthTracker(userRepository, context)
    private val strongAuthTracker = StrongAuthTracker(userRepository, context)


    override val isCurrentUserInLockdown: Flow<Boolean> =
        strongAuthTracker.currentUserAuthFlags.map { it.isInUserLockdown }

    override val authenticationFlags: Flow<AuthenticationFlags> =
    override val authenticationFlags: Flow<AuthenticationFlags> =
        strongAuthTracker.currentUserAuthFlags
        strongAuthTracker.currentUserAuthFlags


    override val isCurrentUserInLockdown: Flow<Boolean> =
        authenticationFlags.map { it.isInUserLockdown }

    override val requiresPrimaryAuthForSecureLockDevice: Flow<Boolean> =
        if (secureLockDevice()) {
            authenticationFlags.map { it.isPrimaryAuthRequiredForSecureLockDevice }
        } else {
            flowOf(false)
        }

    override val requiresStrongBiometricAuthForSecureLockDevice: Flow<Boolean> =
        if (secureLockDevice()) {
            authenticationFlags.map {
                !it.isPrimaryAuthRequiredForSecureLockDevice &&
                    it.isStrongBiometricAuthRequiredForSecureLockDevice &&
                    !it.isPrimaryAuthRequiredAfterLockout
            }
        } else {
            flowOf(false)
        }

    init {
    init {
        Log.d(TAG, "Registering StrongAuthTracker")
        Log.d(TAG, "Registering StrongAuthTracker")
        lockPatternUtils.registerStrongAuthTracker(strongAuthTracker)
        lockPatternUtils.registerStrongAuthTracker(strongAuthTracker)
+19 −7
Original line number Original line Diff line number Diff line
@@ -23,7 +23,7 @@ data class AuthenticationFlags(val userId: Int, val flag: Int) {
    val isInUserLockdown =
    val isInUserLockdown =
        containsFlag(
        containsFlag(
            flag,
            flag,
            LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
            LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
        )
        )


    val isPrimaryAuthRequiredAfterReboot =
    val isPrimaryAuthRequiredAfterReboot =
@@ -38,7 +38,19 @@ data class AuthenticationFlags(val userId: Int, val flag: Int) {
    val isPrimaryAuthRequiredAfterDpmLockdown =
    val isPrimaryAuthRequiredAfterDpmLockdown =
        containsFlag(
        containsFlag(
            flag,
            flag,
            LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW
            LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW,
        )

    val isPrimaryAuthRequiredForSecureLockDevice =
        containsFlag(
            flag,
            LockPatternUtils.StrongAuthTracker.PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE,
        )

    val isStrongBiometricAuthRequiredForSecureLockDevice =
        containsFlag(
            flag,
            LockPatternUtils.StrongAuthTracker.STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE,
        )
        )


    val someAuthRequiredAfterUserRequest =
    val someAuthRequiredAfterUserRequest =
@@ -47,13 +59,13 @@ data class AuthenticationFlags(val userId: Int, val flag: Int) {
    val someAuthRequiredAfterTrustAgentExpired =
    val someAuthRequiredAfterTrustAgentExpired =
        containsFlag(
        containsFlag(
            flag,
            flag,
            LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED
            LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED,
        )
        )


    val isPrimaryAuthRequiredForUnattendedUpdate =
    val isPrimaryAuthRequiredForUnattendedUpdate =
        containsFlag(
        containsFlag(
            flag,
            flag,
            LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
            LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE,
        )
        )


    /** Either Class 3 biometrics or primary auth can be used to unlock the device. */
    /** Either Class 3 biometrics or primary auth can be used to unlock the device. */
@@ -61,19 +73,19 @@ data class AuthenticationFlags(val userId: Int, val flag: Int) {
        containsFlag(
        containsFlag(
            flag,
            flag,
            LockPatternUtils.StrongAuthTracker
            LockPatternUtils.StrongAuthTracker
                .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
                .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT,
        )
        )


    val isSomeAuthRequiredAfterAdaptiveAuthRequest =
    val isSomeAuthRequiredAfterAdaptiveAuthRequest =
        containsFlag(
        containsFlag(
            flag,
            flag,
            LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST
            LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST,
        )
        )


    val isSomeAuthRequiredAfterWatchDisconnected =
    val isSomeAuthRequiredAfterWatchDisconnected =
        containsFlag(
        containsFlag(
            flag,
            flag,
            LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_WATCH_DISCONNECTED
            LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_WATCH_DISCONNECTED,
        )
        )
}
}


+2 −0
Original line number Original line Diff line number Diff line
@@ -37,10 +37,12 @@ interface SecureLockDeviceModule {
        fun providesSecureLockDeviceRepository(
        fun providesSecureLockDeviceRepository(
            @Background backgroundExecutor: Executor,
            @Background backgroundExecutor: Executor,
            authenticationPolicyManager: AuthenticationPolicyManager?,
            authenticationPolicyManager: AuthenticationPolicyManager?,
            biometricSettingsRepository: BiometricSettingsRepository,
        ): SecureLockDeviceRepository {
        ): SecureLockDeviceRepository {
            return SecureLockDeviceRepositoryImpl(
            return SecureLockDeviceRepositoryImpl(
                backgroundExecutor = backgroundExecutor,
                backgroundExecutor = backgroundExecutor,
                authenticationPolicyManager = authenticationPolicyManager,
                authenticationPolicyManager = authenticationPolicyManager,
                biometricSettingsRepository = biometricSettingsRepository,
            )
            )
        }
        }


Loading