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

Commit ed1479cc authored by Chandru S's avatar Chandru S
Browse files

Make current user strong auth flags available in BiometricSettingsRepository

Other changes:
 - Face auth enabled check is now user specific.

Bug: 275600559
Test: atest BiometricSettingsRepositoryTest
Change-Id: Ia9ec3ce22e7b03ba476a7044a380bbeaf5bb2edc
parent 366498dd
Loading
Loading
Loading
Loading
+30 −26
Original line number Diff line number Diff line
@@ -25,7 +25,6 @@ import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
import android.os.UserHandle
import android.util.Log
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.biometrics.AuthController
@@ -36,13 +35,14 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.TAG
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.keyguard.shared.model.DevicePosture
import com.android.systemui.user.data.repository.UserRepository
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -108,10 +108,14 @@ interface BiometricSettingsRepository {
     * lockdown.
     */
    val isCurrentUserInLockdown: Flow<Boolean>

    /** Authentication flags set for the current user. */
    val authenticationFlags: Flow<AuthenticationFlags>
}

const val TAG = "BiometricsRepositoryImpl"
private const val TAG = "BiometricsRepositoryImpl"

@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class BiometricSettingsRepositoryImpl
@Inject
@@ -129,6 +133,8 @@ constructor(
    dumpManager: DumpManager,
) : BiometricSettingsRepository, Dumpable {

    private val biometricsEnabledForUser = mutableMapOf<Int, Boolean>()

    override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>

    private val strongAuthTracker = StrongAuthTracker(userRepository, context)
@@ -136,6 +142,9 @@ constructor(
    override val isCurrentUserInLockdown: Flow<Boolean> =
        strongAuthTracker.currentUserAuthFlags.map { it.isInUserLockdown }

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

    init {
        Log.d(TAG, "Registering StrongAuthTracker")
        lockPatternUtils.registerStrongAuthTracker(strongAuthTracker)
@@ -231,9 +240,14 @@ constructor(
            }
        }

    private val isFaceEnabledByBiometricsManagerForCurrentUser: Flow<Boolean> =
        userRepository.selectedUserInfo.flatMapLatest { userInfo ->
            isFaceEnabledByBiometricsManager.map { biometricsEnabledForUser[userInfo.id] ?: false }
        }

    override val isFaceAuthenticationEnabled: Flow<Boolean>
        get() =
            combine(isFaceEnabledByBiometricsManager, isFaceEnabledByDevicePolicy) {
            combine(isFaceEnabledByBiometricsManagerForCurrentUser, isFaceEnabledByDevicePolicy) {
                biometricsManagerSetting,
                devicePolicySetting ->
                biometricsManagerSetting && devicePolicySetting
@@ -249,13 +263,13 @@ constructor(
            .flowOn(backgroundDispatcher)
            .distinctUntilChanged()

    private val isFaceEnabledByBiometricsManager =
    private val isFaceEnabledByBiometricsManager: Flow<Pair<Int, Boolean>> =
        conflatedCallbackFlow {
                val callback =
                    object : IBiometricEnabledOnKeyguardCallback.Stub() {
                        override fun onChanged(enabled: Boolean, userId: Int) {
                            trySendWithFailureLogging(
                                enabled,
                                Pair(userId, enabled),
                                TAG,
                                "biometricsEnabled state changed"
                            )
@@ -264,9 +278,10 @@ constructor(
                biometricManager?.registerEnabledOnKeyguardCallback(callback)
                awaitClose {}
            }
            .onEach { biometricsEnabledForUser[it.first] = it.second }
            // This is because the callback is binder-based and we want to avoid multiple callbacks
            // being registered.
            .stateIn(scope, SharingStarted.Eagerly, false)
            .stateIn(scope, SharingStarted.Eagerly, Pair(0, false))

    override val isStrongBiometricAllowed: StateFlow<Boolean> =
        strongAuthTracker.isStrongBiometricAllowed.stateIn(
@@ -306,14 +321,13 @@ constructor(
            )
}

@OptIn(ExperimentalCoroutinesApi::class)
private class StrongAuthTracker(private val userRepository: UserRepository, context: Context?) :
    LockPatternUtils.StrongAuthTracker(context) {

    // Backing field for onStrongAuthRequiredChanged
    private val _strongAuthFlags =
        MutableStateFlow(
            StrongAuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId))
        )
    private val _authFlags =
        MutableStateFlow(AuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId)))

    // Backing field for onIsNonStrongBiometricAllowedChanged
    private val _nonStrongBiometricAllowed =
@@ -321,17 +335,15 @@ private class StrongAuthTracker(private val userRepository: UserRepository, cont
            Pair(currentUserId, isNonStrongBiometricAllowedAfterIdleTimeout(currentUserId))
        )

    val currentUserAuthFlags: Flow<StrongAuthenticationFlags> =
    val currentUserAuthFlags: Flow<AuthenticationFlags> =
        userRepository.selectedUserInfo
            .map { it.id }
            .distinctUntilChanged()
            .flatMapLatest { userId ->
                _strongAuthFlags
                    .filter { it.userId == userId }
                _authFlags
                    .map { AuthenticationFlags(userId, getStrongAuthForUser(userId)) }
                    .onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") }
                    .onStart {
                        emit(StrongAuthenticationFlags(userId, getStrongAuthForUser(userId)))
                    }
                    .onStart { emit(AuthenticationFlags(userId, getStrongAuthForUser(userId))) }
            }

    /** isStrongBiometricAllowed for the current user. */
@@ -356,7 +368,7 @@ private class StrongAuthTracker(private val userRepository: UserRepository, cont

    override fun onStrongAuthRequiredChanged(userId: Int) {
        val newFlags = getStrongAuthForUser(userId)
        _strongAuthFlags.value = StrongAuthenticationFlags(userId, newFlags)
        _authFlags.value = AuthenticationFlags(userId, newFlags)
        Log.d(TAG, "onStrongAuthRequiredChanged for userId: $userId, flag value: $newFlags")
    }

@@ -375,11 +387,3 @@ private fun DevicePolicyManager.isFingerprintDisabled(userId: Int): Boolean =

private fun DevicePolicyManager.isNotActive(userId: Int, policy: Int): Boolean =
    (getKeyguardDisabledFeatures(null, userId) and policy) == 0

private data class StrongAuthenticationFlags(val userId: Int, val flag: Int) {
    val isInUserLockdown = containsFlag(flag, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN)
}

private fun containsFlag(haystack: Int, needle: Int): Boolean {
    return haystack and needle != 0
}
+70 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.keyguard.shared.model

import com.android.internal.widget.LockPatternUtils

/** Authentication flags corresponding to a user. */
data class AuthenticationFlags(val userId: Int, val flag: Int) {
    val isInUserLockdown =
        containsFlag(
            flag,
            LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
        )

    val isPrimaryAuthRequiredAfterReboot =
        containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT)

    val isPrimaryAuthRequiredAfterTimeout =
        containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT)

    val isPrimaryAuthRequiredAfterDpmLockdown =
        containsFlag(
            flag,
            LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW
        )

    val someAuthRequiredAfterUserRequest =
        containsFlag(flag, LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST)

    val someAuthRequiredAfterTrustAgentExpired =
        containsFlag(
            flag,
            LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED
        )

    val primaryAuthRequiredAfterLockout =
        containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT)

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

    /** Either Class 3 biometrics or primary auth can be used to unlock the device. */
    val strongerAuthRequiredAfterNonStrongBiometricsTimeout =
        containsFlag(
            flag,
            LockPatternUtils.StrongAuthTracker
                .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
        )
}

private fun containsFlag(haystack: Int, needle: Int): Boolean {
    return haystack and needle != 0
}
+56 −5
Original line number Diff line number Diff line
@@ -310,19 +310,38 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
            createBiometricSettingsRepository()
            verify(biometricManager)
                .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())

            whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
                .thenReturn(0)
            broadcastDPMStateChange()
            val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)

            assertThat(isFaceAuthEnabled()).isFalse()

            // Value changes for another user
            biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)

            assertThat(isFaceAuthEnabled()).isFalse()

            // Value changes for current user.
            biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
            val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)

            assertThat(isFaceAuthEnabled()).isTrue()
        }

    @Test
    fun userChange_biometricEnabledChange_handlesRaceCondition() =
        testScope.runTest {
            createBiometricSettingsRepository()
            verify(biometricManager)
                .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
            val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
            biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
            runCurrent()

            biometricManagerCallback.value.onChanged(false, PRIMARY_USER_ID)
            userRepository.setSelectedUserInfo(ANOTHER_USER)
            runCurrent()

            assertThat(isFaceAuthEnabled()).isFalse()
            assertThat(isFaceAuthEnabled()).isTrue()
        }

    @Test
@@ -382,7 +401,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
        }

    @Test
    fun userInLockdownUsesStrongAuthFlagsToDetermineValue() =
    fun userInLockdownUsesAuthFlagsToDetermineValue() =
        testScope.runTest {
            createBiometricSettingsRepository()

@@ -405,6 +424,38 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
            assertThat(isUserInLockdown()).isTrue()
        }

    @Test
    fun authFlagChangesForCurrentUserArePropagated() =
        testScope.runTest {
            createBiometricSettingsRepository()

            val authFlags = collectLastValue(underTest.authenticationFlags)
            // has default value.
            val defaultStrongAuthValue = STRONG_AUTH_REQUIRED_AFTER_BOOT
            assertThat(authFlags()!!.flag).isEqualTo(defaultStrongAuthValue)

            // change strong auth flags for another user.
            // Combine with one more flag to check if we do the bitwise and
            val inLockdown =
                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN or STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
            onStrongAuthChanged(inLockdown, ANOTHER_USER_ID)

            // Still false.
            assertThat(authFlags()!!.flag).isEqualTo(defaultStrongAuthValue)

            // change strong auth flags for current user.
            onStrongAuthChanged(inLockdown, PRIMARY_USER_ID)

            assertThat(authFlags()!!.flag).isEqualTo(inLockdown)

            onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, ANOTHER_USER_ID)

            assertThat(authFlags()!!.flag).isEqualTo(inLockdown)

            userRepository.setSelectedUserInfo(ANOTHER_USER)
            assertThat(authFlags()!!.flag).isEqualTo(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT)
        }

    private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) {
        authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled)
    }
+28 −3
Original line number Diff line number Diff line
@@ -17,10 +17,13 @@

package com.android.systemui.keyguard.data.repository

import com.android.internal.widget.LockPatternUtils
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map

class FakeBiometricSettingsRepository : BiometricSettingsRepository {

@@ -50,9 +53,12 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
    override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
        get() = _isFaceAuthSupportedInCurrentPosture

    private val _isCurrentUserInLockdown = MutableStateFlow(false)
    override val isCurrentUserInLockdown: Flow<Boolean>
        get() = _isCurrentUserInLockdown
        get() = _authFlags.map { it.isInUserLockdown }

    private val _authFlags = MutableStateFlow(AuthenticationFlags(0, 0))
    override val authenticationFlags: Flow<AuthenticationFlags>
        get() = _authFlags

    fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) {
        _isFingerprintEnrolled.value = isFingerprintEnrolled
@@ -66,6 +72,10 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
        _isFingerprintEnabledByDevicePolicy.value = isFingerprintEnabledByDevicePolicy
    }

    fun setAuthenticationFlags(value: AuthenticationFlags) {
        _authFlags.value = value
    }

    fun setFaceEnrolled(isFaceEnrolled: Boolean) {
        _isFaceEnrolled.value = isFaceEnrolled
    }
@@ -79,7 +89,22 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
    }

    fun setIsUserInLockdown(value: Boolean) {
        _isCurrentUserInLockdown.value = value
        if (value) {
            setAuthenticationFlags(
                AuthenticationFlags(
                    _authFlags.value.userId,
                    _authFlags.value.flag or
                        LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
                )
            )
        } else {
            setAuthenticationFlags(
                AuthenticationFlags(
                    _authFlags.value.userId,
                    LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
                )
            )
        }
    }

    fun setIsNonStrongBiometricAllowed(value: Boolean) {