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

Commit db7568e3 authored by Alejandro Nijamkin's avatar Alejandro Nijamkin
Browse files

[flexiglass] Hooks up real app state and logic in keyguard (3)

- Connects the unlock logic to Flexiglass to let it unlock the device.

Bug: 280883900
Test: Unit tests still pass.
Test: manually verified that unlocking the device through the Flexiglass
bouncer actually unlocks the actual device.
Test: manually verified that locking the device using the power button (with "instantly lock device" setting both
on and off) properly locks the device and moves back to the Lockscreen scene that shows the
lock icon as locked.
Test: verified that above with no lock screen, swipe method, PIN,
pattern, and password methods.

Change-Id: I1cfb71ab31cf2ddda54c8009656a58b3f64e6fa6
parent d3813b18
Loading
Loading
Loading
Loading
+42 −1
Original line number Diff line number Diff line
@@ -79,17 +79,25 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteracto
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.model.SceneContainerNames;
import com.android.systemui.scene.shared.model.SceneKey;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.user.domain.interactor.UserInteractor;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.GlobalSettings;

import java.io.File;
import java.util.Optional;

import javax.inject.Inject;
import javax.inject.Provider;

import kotlinx.coroutines.Job;

/** Controller for {@link KeyguardSecurityContainer} */
@KeyguardBouncerScope
@@ -378,6 +386,10 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
                    showPrimarySecurityScreen(false);
                }
            };
    private final UserInteractor mUserInteractor;
    private final Provider<SceneInteractor> mSceneInteractor;
    private final Provider<JavaAdapter> mJavaAdapter;
    @Nullable private Job mSceneTransitionCollectionJob;

    @Inject
    public KeyguardSecurityContainerController(KeyguardSecurityContainer view,
@@ -402,7 +414,10 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
            ViewMediatorCallback viewMediatorCallback,
            AudioManager audioManager,
            KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
            BouncerMessageInteractor bouncerMessageInteractor
            BouncerMessageInteractor bouncerMessageInteractor,
            Provider<JavaAdapter> javaAdapter,
            UserInteractor userInteractor,
            Provider<SceneInteractor> sceneInteractor
    ) {
        super(view);
        mLockPatternUtils = lockPatternUtils;
@@ -429,6 +444,9 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
        mAudioManager = audioManager;
        mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
        mBouncerMessageInteractor = bouncerMessageInteractor;
        mUserInteractor = userInteractor;
        mSceneInteractor = sceneInteractor;
        mJavaAdapter = javaAdapter;
    }

    @Override
@@ -451,6 +469,24 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
        mView.setOnKeyListener(mOnKeyListener);

        showPrimarySecurityScreen(false);

        if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
            // When the scene framework transitions from bouncer to gone, we dismiss the keyguard.
            mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
                mSceneInteractor.get().sceneTransitions(SceneContainerNames.SYSTEM_UI_DEFAULT),
                sceneTransitionModel -> {
                    if (sceneTransitionModel != null
                            && sceneTransitionModel.getFrom() == SceneKey.Bouncer.INSTANCE
                            && sceneTransitionModel.getTo() == SceneKey.Gone.INSTANCE) {
                        final int selectedUserId = mUserInteractor.getSelectedUserId();
                        showNextSecurityScreenOrFinish(
                                /* authenticated= */ true,
                                selectedUserId,
                                /* bypassSecondaryLockScreen= */ true,
                                mSecurityModel.getSecurityMode(selectedUserId));
                    }
                });
        }
    }

    @Override
@@ -459,6 +495,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
        mConfigurationController.removeCallback(mConfigurationListener);
        mView.removeMotionEventListener(mGlobalTouchListener);
        mUserSwitcherController.removeUserSwitchCallback(mUserSwitchCallback);

        if (mSceneTransitionCollectionJob != null) {
            mSceneTransitionCollectionJob.cancel(null);
            mSceneTransitionCollectionJob = null;
        }
    }

    /** */
+181 −38
Original line number Diff line number Diff line
@@ -14,25 +14,39 @@
 * limitations under the License.
 */

@file:OptIn(ExperimentalCoroutinesApi::class)

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

import com.android.internal.widget.LockPatternChecker
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockscreenCredential
import com.android.keyguard.KeyguardSecurityModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.time.SystemClock
import dagger.Binds
import dagger.Module
import java.util.function.Function
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext

@@ -57,11 +71,17 @@ interface AuthenticationRepository {
     */
    val isBypassEnabled: StateFlow<Boolean>

    /**
     * Number of consecutively failed authentication attempts. This resets to `0` when
     * authentication succeeds.
     */
    val failedAuthenticationAttempts: StateFlow<Int>
    /** Whether the auto confirm feature is enabled for the currently-selected user. */
    val isAutoConfirmEnabled: StateFlow<Boolean>

    /** The length of the PIN for which we should show a hint. */
    val hintedPinLength: Int

    /** Whether the pattern should be visible for the currently-selected user. */
    val isPatternVisible: StateFlow<Boolean>

    /** The current throttling state, as cached via [setThrottling]. */
    val throttling: StateFlow<AuthenticationThrottlingModel>

    /**
     * Returns the currently-configured authentication method. This determines how the
@@ -69,11 +89,48 @@ interface AuthenticationRepository {
     */
    suspend fun getAuthenticationMethod(): AuthenticationMethodModel

    /** Returns the length of the PIN or `0` if the current auth method is not PIN. */
    suspend fun getPinLength(): Int

    /**
     * Returns whether the lockscreen is enabled.
     *
     * When the lockscreen is not enabled, it shouldn't show in cases when the authentication method
     * is considered not secure (for example, "swipe" is considered to be "none").
     */
    suspend fun isLockscreenEnabled(): Boolean

    /** See [isBypassEnabled]. */
    fun setBypassEnabled(isBypassEnabled: Boolean)

    /** See [failedAuthenticationAttempts]. */
    fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int)
    /** Reports an authentication attempt. */
    suspend fun reportAuthenticationAttempt(isSuccessful: Boolean)

    /** Returns the current number of failed authentication attempts. */
    suspend fun getFailedAuthenticationAttemptCount(): Int

    /**
     * Returns the timestamp for when the current throttling will end, allowing the user to attempt
     * authentication again.
     *
     * Note that this is in milliseconds and it matches [SystemClock.elapsedRealtime].
     */
    suspend fun getThrottlingEndTimestamp(): Long

    /** Sets the cached throttling state, updating the [throttling] flow. */
    fun setThrottling(throttlingModel: AuthenticationThrottlingModel)

    /**
     * Sets the throttling timeout duration (time during which the user should not be allowed to
     * attempt authentication).
     */
    suspend fun setThrottleDuration(durationMs: Int)

    /**
     * Checks the given [LockscreenCredential] to see if it's correct, returning an
     * [AuthenticationResultModel] representing what happened.
     */
    suspend fun checkCredential(credential: LockscreenCredential): AuthenticationResultModel
}

class AuthenticationRepositoryImpl
@@ -83,8 +140,8 @@ constructor(
    private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
    private val userRepository: UserRepository,
    private val lockPatternUtils: LockPatternUtils,
    keyguardRepository: KeyguardRepository,
    private val lockPatternUtils: LockPatternUtils,
) : AuthenticationRepository {

    override val isUnlocked: StateFlow<Boolean> =
@@ -94,54 +151,140 @@ constructor(
            initialValue = false,
        )

    override suspend fun isLockscreenEnabled(): Boolean {
        return withContext(backgroundDispatcher) {
            val selectedUserId = userRepository.selectedUserId
            !lockPatternUtils.isLockPatternEnabled(selectedUserId)
        }
    }

    private val _isBypassEnabled = MutableStateFlow(false)
    override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled.asStateFlow()

    private val _failedAuthenticationAttempts = MutableStateFlow(0)
    override val failedAuthenticationAttempts: StateFlow<Int> =
        _failedAuthenticationAttempts.asStateFlow()
    override val isAutoConfirmEnabled: StateFlow<Boolean> =
        userRepository.selectedUserInfo
            .map { it.id }
            .flatMapLatest { userId ->
                flow { emit(lockPatternUtils.isAutoPinConfirmEnabled(userId)) }
                    .flowOn(backgroundDispatcher)
            }
            .stateIn(
                scope = applicationScope,
                started = SharingStarted.WhileSubscribed(),
                initialValue = true,
            )

    override val hintedPinLength: Int = LockPatternUtils.MIN_AUTO_PIN_REQUIREMENT_LENGTH

    override val isPatternVisible: StateFlow<Boolean> =
        userRepository.selectedUserInfo
            .map { it.id }
            .flatMapLatest { userId ->
                flow { emit(lockPatternUtils.isVisiblePatternEnabled(userId)) }
                    .flowOn(backgroundDispatcher)
            }
            .stateIn(
                scope = applicationScope,
                started = SharingStarted.WhileSubscribed(),
                initialValue = true,
            )

    private val _throttling = MutableStateFlow(AuthenticationThrottlingModel())
    override val throttling: StateFlow<AuthenticationThrottlingModel> = _throttling.asStateFlow()

    private val UserRepository.selectedUserId: Int
        get() = getSelectedUserInfo().id

    override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
        return withContext(backgroundDispatcher) {
            val selectedUserId = userRepository.getSelectedUserInfo().id
            val selectedUserId = userRepository.selectedUserId
            when (getSecurityMode.apply(selectedUserId)) {
                KeyguardSecurityModel.SecurityMode.PIN,
                KeyguardSecurityModel.SecurityMode.SimPin ->
                    AuthenticationMethodModel.Pin(
                        code = listOf(1, 2, 3, 4), // TODO(b/280883900): remove this
                        autoConfirm = lockPatternUtils.isAutoPinConfirmEnabled(selectedUserId),
                    )
                KeyguardSecurityModel.SecurityMode.Password,
                KeyguardSecurityModel.SecurityMode.SimPuk ->
                    AuthenticationMethodModel.Password(
                        password = "password", // TODO(b/280883900): remove this
                    )
                KeyguardSecurityModel.SecurityMode.Pattern ->
                    AuthenticationMethodModel.Pattern(
                        coordinates =
                            listOf(
                                AuthenticationMethodModel.Pattern.PatternCoordinate(2, 0),
                                AuthenticationMethodModel.Pattern.PatternCoordinate(2, 1),
                                AuthenticationMethodModel.Pattern.PatternCoordinate(2, 2),
                                AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1),
                                AuthenticationMethodModel.Pattern.PatternCoordinate(0, 0),
                                AuthenticationMethodModel.Pattern.PatternCoordinate(0, 1),
                                AuthenticationMethodModel.Pattern.PatternCoordinate(0, 2),
                            ), // TODO(b/280883900): remove this
                    )
                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!")
                null -> error("Invalid security is null!")
            }
        }
    }

    override suspend fun getPinLength(): Int {
        return withContext(backgroundDispatcher) {
            val selectedUserId = userRepository.selectedUserId
            lockPatternUtils.getPinLength(selectedUserId)
        }
    }

    override fun setBypassEnabled(isBypassEnabled: Boolean) {
        _isBypassEnabled.value = isBypassEnabled
    }

    override fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int) {
        _failedAuthenticationAttempts.value = failedAuthenticationAttempts
    override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
        val selectedUserId = userRepository.selectedUserId
        withContext(backgroundDispatcher) {
            if (isSuccessful) {
                lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId)
            } else {
                lockPatternUtils.reportFailedPasswordAttempt(selectedUserId)
            }
        }
    }

    override suspend fun getFailedAuthenticationAttemptCount(): Int {
        return withContext(backgroundDispatcher) {
            val selectedUserId = userRepository.selectedUserId
            lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId)
        }
    }

    override suspend fun getThrottlingEndTimestamp(): Long {
        return withContext(backgroundDispatcher) {
            val selectedUserId = userRepository.selectedUserId
            lockPatternUtils.getLockoutAttemptDeadline(selectedUserId)
        }
    }

    override fun setThrottling(throttlingModel: AuthenticationThrottlingModel) {
        _throttling.value = throttlingModel
    }

    override suspend fun setThrottleDuration(durationMs: Int) {
        withContext(backgroundDispatcher) {
            lockPatternUtils.setLockoutAttemptDeadline(
                userRepository.selectedUserId,
                durationMs,
            )
        }
    }

    override suspend fun checkCredential(
        credential: LockscreenCredential
    ): AuthenticationResultModel {
        return suspendCoroutine { continuation ->
            LockPatternChecker.checkCredential(
                lockPatternUtils,
                credential,
                userRepository.selectedUserId,
                object : LockPatternChecker.OnCheckCallback {
                    override fun onChecked(matched: Boolean, throttleTimeoutMs: Int) {
                        continuation.resume(
                            AuthenticationResultModel(
                                isSuccessful = matched,
                                throttleDurationMs = throttleTimeoutMs,
                            )
                        )
                    }

                    override fun onCancelled() {
                        continuation.resume(AuthenticationResultModel(isSuccessful = false))
                    }

                    override fun onEarlyMatched() = Unit
                }
            )
        }
    }
}

+165 −63

File changed.

Preview size limit exceeded, changes collapsed.

+3 −24
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

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

import androidx.annotation.VisibleForTesting

/** Enumerates all known authentication methods. */
sealed class AuthenticationMethodModel(
    /**
@@ -34,30 +32,11 @@ sealed class AuthenticationMethodModel(
    /** The most basic authentication method. The lock screen can be swiped away when displayed. */
    object Swipe : AuthenticationMethodModel(isSecure = false)

    /**
     * Authentication method using a PIN.
     *
     * In practice, a pin is restricted to 16 decimal digits , see
     * [android.app.admin.DevicePolicyManager.MAX_PASSWORD_LENGTH]
     */
    data class Pin(val code: List<Int>, val autoConfirm: Boolean) :
        AuthenticationMethodModel(isSecure = true) {

        /** Convenience constructor for tests only. */
        @VisibleForTesting
        constructor(
            code: Long,
            autoConfirm: Boolean = false
        ) : this(code.toString(10).map { it - '0' }, autoConfirm) {}
    }

    data class Password(val password: String) : AuthenticationMethodModel(isSecure = true)
    object Pin : AuthenticationMethodModel(isSecure = true)

    data class Pattern(
        val coordinates: List<PatternCoordinate>,
        val isPatternVisible: Boolean = true,
    ) : AuthenticationMethodModel(isSecure = true) {
    object Password : AuthenticationMethodModel(isSecure = true)

    object Pattern : AuthenticationMethodModel(isSecure = true) {
        data class PatternCoordinate(
            val x: Int,
            val y: Int,
+25 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 * 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.
@@ -14,17 +14,12 @@
 * limitations under the License.
 */

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

/**
 * Models application state for when further authentication attempts are being throttled due to too
 * many consecutive failed authentication attempts.
 */
data class AuthenticationThrottledModel(
    /** Total number of failed attempts so far. */
    val failedAttemptCount: Int,
    /** Total amount of time the user has to wait before attempting again. */
    val totalDurationSec: Int,
    /** Remaining amount of time the user has to wait before attempting again. */
    val remainingDurationSec: Int,
/** Models the result of an authentication attempt. */
data class AuthenticationResultModel(
    /** Whether authentication was successful. */
    val isSuccessful: Boolean = false,
    /** If [isSuccessful] is `false`, how long the user must wait before trying again. */
    val throttleDurationMs: Int = 0,
)
Loading