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

Commit b91eb315 authored by Ale Nijamkin's avatar Ale Nijamkin Committed by Android (Google) Code Review
Browse files

Merge changes from topic "flexiglass-authmethod-is-a-flow" into udc-qpr-dev

* changes:
  [flexiglass] Skip lockscreen when devices wakes.
  [flexiglass] Adds authmethod flow.
parents 0646b88f a5ed8c10
Loading
Loading
Loading
Loading
+6 −6
Original line number Original line Diff line number Diff line
@@ -63,7 +63,7 @@ constructor(
            .stateIn(
            .stateIn(
                scope = applicationScope,
                scope = applicationScope,
                started = SharingStarted.Eagerly,
                started = SharingStarted.Eagerly,
                initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value)
                initialValue = destinationScenes(up = null)
            )
            )


    @Composable
    @Composable
@@ -77,12 +77,12 @@ constructor(
    }
    }


    private fun destinationScenes(
    private fun destinationScenes(
        up: SceneKey,
        up: SceneKey?,
    ): Map<UserAction, SceneModel> {
    ): Map<UserAction, SceneModel> {
        return mapOf(
        return buildMap {
            UserAction.Swipe(Direction.UP) to SceneModel(up),
            up?.let { this[UserAction.Swipe(Direction.UP)] = SceneModel(up) }
            UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade)
            this[UserAction.Swipe(Direction.DOWN)] = SceneModel(SceneKey.Shade)
        )
        }
    }
    }
}
}


+37 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright 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.authentication.data.model

/** Enumerates all known authentication methods. */
sealed class AuthenticationMethodModel(
    /**
     * Whether the authentication method is considered to be "secure".
     *
     * "Secure" authentication methods require authentication to unlock the device. Non-secure auth
     * methods simply require user dismissal.
     */
    open val isSecure: Boolean,
) {
    /** There is no authentication method on the device. We shouldn't even show the lock screen. */
    object None : AuthenticationMethodModel(isSecure = false)

    object Pin : AuthenticationMethodModel(isSecure = true)

    object Password : AuthenticationMethodModel(isSecure = true)

    object Pattern : AuthenticationMethodModel(isSecure = true)
}
+83 −16
Original line number Original line Diff line number Diff line
@@ -14,15 +14,21 @@
 * limitations under the License.
 * limitations under the License.
 */
 */


@file:OptIn(ExperimentalCoroutinesApi::class)

package com.android.systemui.authentication.data.repository
package com.android.systemui.authentication.data.repository


import android.app.admin.DevicePolicyManager
import android.content.IntentFilter
import android.os.UserHandle
import com.android.internal.widget.LockPatternChecker
import com.android.internal.widget.LockPatternChecker
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockscreenCredential
import com.android.internal.widget.LockscreenCredential
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.data.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -37,13 +43,17 @@ import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withContext


@@ -54,9 +64,10 @@ interface AuthenticationRepository {
     * Whether the device is unlocked.
     * Whether the device is unlocked.
     *
     *
     * A device that is not yet unlocked requires unlocking by completing an authentication
     * A device that is not yet unlocked requires unlocking by completing an authentication
     * challenge according to the current authentication method.
     * challenge according to the current authentication method, unless in cases when the current
     *
     * authentication method is not "secure" (for example, None); in such cases, the value of this
     * Note that this state has no real bearing on whether the lockscreen is showing or dismissed.
     * flow will always be `true`, even if the lockscreen is showing and still needs to be dismissed
     * by the user to proceed.
     */
     */
    val isUnlocked: StateFlow<Boolean>
    val isUnlocked: StateFlow<Boolean>


@@ -85,9 +96,30 @@ interface AuthenticationRepository {
    /** The current throttling state, as cached via [setThrottling]. */
    /** The current throttling state, as cached via [setThrottling]. */
    val throttling: StateFlow<AuthenticationThrottlingModel>
    val throttling: StateFlow<AuthenticationThrottlingModel>


    /**
     * The currently-configured authentication method. This determines how the authentication
     * challenge needs to be completed in order to unlock an otherwise locked device.
     *
     * Note: there may be other ways to unlock the device that "bypass" the need for this
     * authentication challenge (notably, biometrics like fingerprint or face unlock).
     *
     * Note: by design, this is a [Flow] and not a [StateFlow]; a consumer who wishes to get a
     * snapshot of the current authentication method without establishing a collector of the flow
     * can do so by invoking [getAuthenticationMethod].
     */
    val authenticationMethod: Flow<AuthenticationMethodModel>

    /**
    /**
     * Returns the currently-configured authentication method. This determines how the
     * Returns the currently-configured authentication method. This determines how the
     * authentication challenge is completed in order to unlock an otherwise locked device.
     * authentication challenge needs to be completed in order to unlock an otherwise locked device.
     *
     * Note: there may be other ways to unlock the device that "bypass" the need for this
     * authentication challenge (notably, biometrics like fingerprint or face unlock).
     *
     * Note: by design, this is offered as a convenience method alongside [authenticationMethod].
     * The flow should be used for code that wishes to stay up-to-date its logic as the
     * authentication changes over time and this method should be used for simple code that only
     * needs to check the current value.
     */
     */
    suspend fun getAuthenticationMethod(): AuthenticationMethodModel
    suspend fun getAuthenticationMethod(): AuthenticationMethodModel


@@ -141,6 +173,7 @@ constructor(
    private val userRepository: UserRepository,
    private val userRepository: UserRepository,
    keyguardRepository: KeyguardRepository,
    keyguardRepository: KeyguardRepository,
    private val lockPatternUtils: LockPatternUtils,
    private val lockPatternUtils: LockPatternUtils,
    broadcastDispatcher: BroadcastDispatcher,
) : AuthenticationRepository {
) : AuthenticationRepository {


    override val isUnlocked = keyguardRepository.isKeyguardUnlocked
    override val isUnlocked = keyguardRepository.isKeyguardUnlocked
@@ -148,7 +181,7 @@ constructor(
    override suspend fun isLockscreenEnabled(): Boolean {
    override suspend fun isLockscreenEnabled(): Boolean {
        return withContext(backgroundDispatcher) {
        return withContext(backgroundDispatcher) {
            val selectedUserId = userRepository.selectedUserId
            val selectedUserId = userRepository.selectedUserId
            !lockPatternUtils.isLockPatternEnabled(selectedUserId)
            !lockPatternUtils.isLockScreenDisabled(selectedUserId)
        }
        }
    }
    }


@@ -172,18 +205,31 @@ constructor(
    private val UserRepository.selectedUserId: Int
    private val UserRepository.selectedUserId: Int
        get() = getSelectedUserInfo().id
        get() = getSelectedUserInfo().id


    override val authenticationMethod: Flow<AuthenticationMethodModel> =
        userRepository.selectedUserInfo
            .map { it.id }
            .distinctUntilChanged()
            .flatMapLatest { selectedUserId ->
                broadcastDispatcher
                    .broadcastFlow(
                        filter =
                            IntentFilter(
                                DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
                            ),
                        user = UserHandle.of(selectedUserId),
                    )
                    .onStart { emit(Unit) }
                    .map { selectedUserId }
            }
            .map { selectedUserId ->
                withContext(backgroundDispatcher) {
                    blockingAuthenticationMethodInternal(selectedUserId)
                }
            }

    override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
    override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
        return withContext(backgroundDispatcher) {
        return withContext(backgroundDispatcher) {
            val selectedUserId = userRepository.selectedUserId
            blockingAuthenticationMethodInternal(userRepository.selectedUserId)
            when (getSecurityMode.apply(selectedUserId)) {
                KeyguardSecurityModel.SecurityMode.PIN,
                KeyguardSecurityModel.SecurityMode.SimPin,
                KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Pin
                KeyguardSecurityModel.SecurityMode.Password -> AuthenticationMethodModel.Password
                KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
                KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None
                KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!")
            }
        }
        }
    }
    }


@@ -301,6 +347,27 @@ constructor(


        return flow.asStateFlow()
        return flow.asStateFlow()
    }
    }

    /**
     * Returns the authentication method for the given user ID.
     *
     * WARNING: this is actually a blocking IPC/"binder" call that's expensive to do on the main
     * thread. We keep it not marked as `suspend` because we want to be able to run this without a
     * `runBlocking` which has a ton of performance/blocking problems.
     */
    private fun blockingAuthenticationMethodInternal(
        userId: Int,
    ): AuthenticationMethodModel {
        return when (getSecurityMode.apply(userId)) {
            KeyguardSecurityModel.SecurityMode.PIN,
            KeyguardSecurityModel.SecurityMode.SimPin,
            KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Pin
            KeyguardSecurityModel.SecurityMode.Password -> AuthenticationMethodModel.Password
            KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
            KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None
            KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!")
        }
    }
}
}


@Module
@Module
+73 −27
Original line number Original line Diff line number Diff line
@@ -18,8 +18,10 @@ package com.android.systemui.authentication.domain.interactor


import com.android.internal.widget.LockPatternView
import com.android.internal.widget.LockPatternView
import com.android.internal.widget.LockscreenCredential
import com.android.internal.widget.LockscreenCredential
import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
import com.android.systemui.authentication.data.repository.AuthenticationRepository
import com.android.systemui.authentication.data.repository.AuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Application
@@ -35,8 +37,10 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.stateIn
@@ -55,22 +59,41 @@ constructor(
    private val keyguardRepository: KeyguardRepository,
    private val keyguardRepository: KeyguardRepository,
    private val clock: SystemClock,
    private val clock: SystemClock,
) {
) {
    /**
     * The currently-configured authentication method. This determines how the authentication
     * challenge needs to be completed in order to unlock an otherwise locked device.
     *
     * Note: there may be other ways to unlock the device that "bypass" the need for this
     * authentication challenge (notably, biometrics like fingerprint or face unlock).
     *
     * Note: by design, this is a [Flow] and not a [StateFlow]; a consumer who wishes to get a
     * snapshot of the current authentication method without establishing a collector of the flow
     * can do so by invoking [getAuthenticationMethod].
     *
     * Note: this layer adds the synthetic authentication method of "swipe" which is special. When
     * the current authentication method is "swipe", the user does not need to complete any
     * authentication challenge to unlock the device; they just need to dismiss the lockscreen to
     * get past it. This also means that the value of [isUnlocked] remains `false` even when the
     * lockscreen is showing and still needs to be dismissed by the user to proceed.
     */
    val authenticationMethod: Flow<DomainLayerAuthenticationMethodModel> =
        repository.authenticationMethod.map { rawModel -> rawModel.toDomainLayer() }

    /**
    /**
     * Whether the device is unlocked.
     * Whether the device is unlocked.
     *
     *
     * A device that is not yet unlocked requires unlocking by completing an authentication
     * A device that is not yet unlocked requires unlocking by completing an authentication
     * challenge according to the current authentication method.
     * challenge according to the current authentication method, unless in cases when the current
     *
     * authentication method is not "secure" (for example, None and Swipe); in such cases, the value
     * Note that this state has no real bearing on whether the lock screen is showing or dismissed.
     * of this flow will always be `true`, even if the lockscreen is showing and still needs to be
     * dismissed by the user to proceed.
     */
     */
    val isUnlocked: StateFlow<Boolean> =
    val isUnlocked: StateFlow<Boolean> =
        repository.isUnlocked
        combine(
            .map { isUnlocked ->
                repository.isUnlocked,
                if (getAuthenticationMethod() is AuthenticationMethodModel.None) {
                authenticationMethod,
                    true
            ) { isUnlocked, authenticationMethod ->
                } else {
                authenticationMethod is DomainLayerAuthenticationMethodModel.None || isUnlocked
                    isUnlocked
                }
            }
            }
            .stateIn(
            .stateIn(
                scope = applicationScope,
                scope = applicationScope,
@@ -129,18 +152,24 @@ constructor(


    /**
    /**
     * Returns the currently-configured authentication method. This determines how the
     * Returns the currently-configured authentication method. This determines how the
     * authentication challenge is completed in order to unlock an otherwise locked device.
     * authentication challenge needs to be completed in order to unlock an otherwise locked device.
     *
     * Note: there may be other ways to unlock the device that "bypass" the need for this
     * authentication challenge (notably, biometrics like fingerprint or face unlock).
     *
     * Note: by design, this is offered as a convenience method alongside [authenticationMethod].
     * The flow should be used for code that wishes to stay up-to-date its logic as the
     * authentication changes over time and this method should be used for simple code that only
     * needs to check the current value.
     *
     * Note: this layer adds the synthetic authentication method of "swipe" which is special. When
     * the current authentication method is "swipe", the user does not need to complete any
     * authentication challenge to unlock the device; they just need to dismiss the lockscreen to
     * get past it. This also means that the value of [isUnlocked] remains `false` even when the
     * lockscreen is showing and still needs to be dismissed by the user to proceed.
     */
     */
    suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
    suspend fun getAuthenticationMethod(): DomainLayerAuthenticationMethodModel {
        val authMethod = repository.getAuthenticationMethod()
        return repository.getAuthenticationMethod().toDomainLayer()
        return if (
            authMethod is AuthenticationMethodModel.None && repository.isLockscreenEnabled()
        ) {
            // We treat "None" as "Swipe" when the lockscreen is enabled.
            AuthenticationMethodModel.Swipe
        } else {
            authMethod
        }
    }
    }


    /**
    /**
@@ -270,21 +299,38 @@ constructor(
        }
        }
    }
    }


    private fun AuthenticationMethodModel.createCredential(
    private fun DomainLayerAuthenticationMethodModel.createCredential(
        input: List<Any>
        input: List<Any>
    ): LockscreenCredential? {
    ): LockscreenCredential? {
        return when (this) {
        return when (this) {
            is AuthenticationMethodModel.Pin ->
            is DomainLayerAuthenticationMethodModel.Pin ->
                LockscreenCredential.createPin(input.joinToString(""))
                LockscreenCredential.createPin(input.joinToString(""))
            is AuthenticationMethodModel.Password ->
            is DomainLayerAuthenticationMethodModel.Password ->
                LockscreenCredential.createPassword(input.joinToString(""))
                LockscreenCredential.createPassword(input.joinToString(""))
            is AuthenticationMethodModel.Pattern ->
            is DomainLayerAuthenticationMethodModel.Pattern ->
                LockscreenCredential.createPattern(
                LockscreenCredential.createPattern(
                    input
                    input
                        .map { it as AuthenticationMethodModel.Pattern.PatternCoordinate }
                        .map { it as AuthenticationPatternCoordinate }
                        .map { LockPatternView.Cell.of(it.y, it.x) }
                        .map { LockPatternView.Cell.of(it.y, it.x) }
                )
                )
            else -> null
            else -> null
        }
        }
    }
    }

    private suspend fun DataLayerAuthenticationMethodModel.toDomainLayer():
        DomainLayerAuthenticationMethodModel {
        return when (this) {
            is DataLayerAuthenticationMethodModel.None ->
                if (repository.isLockscreenEnabled()) {
                    DomainLayerAuthenticationMethodModel.Swipe
                } else {
                    DomainLayerAuthenticationMethodModel.None
                }
            is DataLayerAuthenticationMethodModel.Pin -> DomainLayerAuthenticationMethodModel.Pin
            is DataLayerAuthenticationMethodModel.Password ->
                DomainLayerAuthenticationMethodModel.Password
            is DataLayerAuthenticationMethodModel.Pattern ->
                DomainLayerAuthenticationMethodModel.Pattern
        }
    }
}
}
+2 −7
Original line number Original line Diff line number Diff line
@@ -14,7 +14,7 @@
 * limitations under the License.
 * limitations under the License.
 */
 */


package com.android.systemui.authentication.shared.model
package com.android.systemui.authentication.domain.model


/** Enumerates all known authentication methods. */
/** Enumerates all known authentication methods. */
sealed class AuthenticationMethodModel(
sealed class AuthenticationMethodModel(
@@ -36,10 +36,5 @@ sealed class AuthenticationMethodModel(


    object Password : AuthenticationMethodModel(isSecure = true)
    object Password : AuthenticationMethodModel(isSecure = true)


    object Pattern : AuthenticationMethodModel(isSecure = true) {
    object Pattern : AuthenticationMethodModel(isSecure = true)
        data class PatternCoordinate(
            val x: Int,
            val y: Int,
        )
    }
}
}
Loading