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

Commit 102064b4 authored by Danny Burakov's avatar Danny Burakov Committed by Android (Google) Code Review
Browse files

Merge "[flexiglass] Add a DeviceEntry module." into main

parents 683becc6 af001916
Loading
Loading
Loading
Loading
+14 −17
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.keyguard;

import static android.app.StatusBarManager.SESSION_KEYGUARD;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;

import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY;
@@ -68,8 +69,6 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.keyguard.dagger.KeyguardBouncerScope;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.res.R;
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor;
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
import com.android.systemui.biometrics.SideFpsController;
import com.android.systemui.biometrics.SideFpsUiRequestSource;
@@ -77,6 +76,7 @@ import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
@@ -84,6 +84,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -420,7 +421,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
                }
            };
    private final UserInteractor mUserInteractor;
    private final Provider<AuthenticationInteractor> mAuthenticationInteractor;
    private final Provider<DeviceEntryInteractor> mDeviceEntryInteractor;
    private final Provider<JavaAdapter> mJavaAdapter;
    private final DeviceProvisionedController mDeviceProvisionedController;
    private final Lazy<PrimaryBouncerInteractor> mPrimaryBouncerInteractor;
@@ -457,7 +458,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
            FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
            KeyguardTransitionInteractor keyguardTransitionInteractor,
            Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor,
            Provider<AuthenticationInteractor> authenticationInteractor
            Provider<DeviceEntryInteractor> deviceEntryInteractor
    ) {
        super(view);
        view.setAccessibilityDelegate(faceAuthAccessibilityDelegate);
@@ -487,7 +488,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
        mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
        mBouncerMessageInteractor = bouncerMessageInteractor;
        mUserInteractor = userInteractor;
        mAuthenticationInteractor = authenticationInteractor;
        mDeviceEntryInteractor = deviceEntryInteractor;
        mJavaAdapter = javaAdapter;
        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
        mDeviceProvisionedController = deviceProvisionedController;
@@ -519,9 +520,9 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
            // When the scene framework says that the lockscreen has been dismissed, dismiss the
            // keyguard here, revealing the underlying app or launcher:
            mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
                mAuthenticationInteractor.get().isLockscreenDismissed(),
                isLockscreenDismissed -> {
                    if (isLockscreenDismissed) {
                mDeviceEntryInteractor.get().isDeviceEntered(),
                    isDeviceEntered -> {
                    if (isDeviceEntered) {
                        final int selectedUserId = mUserInteractor.getSelectedUserId();
                        showNextSecurityScreenOrFinish(
                            /* authenticated= */ true,
@@ -1081,15 +1082,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
     * one side).
     */
    private boolean canUseOneHandedBouncer() {
        switch(mCurrentSecurityMode) {
            case PIN:
            case Pattern:
            case SimPin:
            case SimPuk:
                return getResources().getBoolean(R.bool.can_use_one_handed_bouncer);
            default:
                return false;
        }
        return switch (mCurrentSecurityMode) {
            case PIN, Pattern, SimPin, SimPuk -> getResources().getBoolean(
                    R.bool.can_use_one_handed_bouncer);
            default -> false;
        };
    }

    private boolean canDisplayUserSwitcher() {
+2 −31
Original line number Diff line number Diff line
@@ -29,9 +29,9 @@ import com.android.systemui.authentication.data.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
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.kotlin.pairwise
import com.android.systemui.util.time.SystemClock
@@ -59,18 +59,6 @@ import kotlinx.coroutines.withContext

/** Defines interface for classes that can access authentication-related application state. */
interface AuthenticationRepository {

    /**
     * Whether the device is unlocked.
     *
     * A device that is not yet unlocked requires unlocking by completing an authentication
     * 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
     * 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>

    /**
     * Whether the auto confirm feature is enabled for the currently-selected user.
     *
@@ -129,14 +117,6 @@ interface AuthenticationRepository {
    /** 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

    /** Reports an authentication attempt. */
    suspend fun reportAuthenticationAttempt(isSuccessful: Boolean)

@@ -167,6 +147,7 @@ interface AuthenticationRepository {
    suspend fun checkCredential(credential: LockscreenCredential): AuthenticationResultModel
}

@SysUISingleton
class AuthenticationRepositoryImpl
@Inject
constructor(
@@ -174,20 +155,10 @@ constructor(
    private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
    private val userRepository: UserRepository,
    keyguardRepository: KeyguardRepository,
    private val lockPatternUtils: LockPatternUtils,
    broadcastDispatcher: BroadcastDispatcher,
) : AuthenticationRepository {

    override val isUnlocked = keyguardRepository.isKeyguardUnlocked

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

    override val isAutoConfirmEnabled: StateFlow<Boolean> =
        refreshingFlow(
            initialValue = false,
+22 −104
Original line number Diff line number Diff line
@@ -26,9 +26,7 @@ import com.android.systemui.authentication.shared.model.AuthenticationThrottling
import com.android.systemui.dagger.SysUISingleton
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.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -42,15 +40,19 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/** Hosts application business logic related to authentication. */
/**
 * Hosts application business logic related to user authentication.
 *
 * Note: there is a distinction between authentication (determining a user's identity) and device
 * entry (dismissing the lockscreen). For logic that is specific to device entry, please use
 * `DeviceEntryInteractor` instead.
 */
@SysUISingleton
class AuthenticationInteractor
@Inject
@@ -59,8 +61,7 @@ constructor(
    private val repository: AuthenticationRepository,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
    private val userRepository: UserRepository,
    private val keyguardRepository: KeyguardRepository,
    sceneInteractor: SceneInteractor,
    private val deviceEntryRepository: DeviceEntryRepository,
    private val clock: SystemClock,
) {
    /**
@@ -77,76 +78,13 @@ constructor(
     * 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.
     * get past it. This also means that the value of `DeviceEntryInteractor#isUnlocked` remains
     * `true` 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.
     *
     * A device that is not yet unlocked requires unlocking by completing an authentication
     * 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
     * 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> =
        combine(
                repository.isUnlocked,
                authenticationMethod,
            ) { isUnlocked, authenticationMethod ->
                !authenticationMethod.isSecure || isUnlocked
            }
            .stateIn(
                scope = applicationScope,
                started = SharingStarted.Eagerly,
                initialValue = false,
            )

    /**
     * Whether the lockscreen has been dismissed (by any method). This can be false even when the
     * device is unlocked, e.g. when swipe to unlock is enabled.
     *
     * Note:
     * - `false` doesn't mean the lockscreen is visible (it may be occluded or covered by other UI).
     * - `true` doesn't mean the lockscreen is invisible (since this state changes before the
     *   transition occurs).
     */
    val isLockscreenDismissed: StateFlow<Boolean> =
        sceneInteractor.desiredScene
            .map { it.key }
            .filter { currentScene ->
                currentScene == SceneKey.Gone || currentScene == SceneKey.Lockscreen
            }
            .map { it == SceneKey.Gone }
            .stateIn(
                scope = applicationScope,
                started = SharingStarted.WhileSubscribed(),
                initialValue = false,
            )

    /**
     * Whether it's currently possible to swipe up to dismiss the lockscreen without requiring
     * authentication. This returns false whenever the lockscreen has been dismissed.
     *
     * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other
     * UI.
     */
    val canSwipeToDismiss =
        combine(authenticationMethod, isLockscreenDismissed) {
                authenticationMethod,
                isLockscreenDismissed ->
                authenticationMethod is DomainLayerAuthenticationMethodModel.Swipe &&
                    !isLockscreenDismissed
            }
            .stateIn(
                scope = applicationScope,
                started = SharingStarted.WhileSubscribed(),
                initialValue = false,
            )

    /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */
    val throttling: StateFlow<AuthenticationThrottlingModel> = repository.throttling

@@ -211,31 +149,14 @@ constructor(
     * 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.
     * get past it. This also means that the value of `DeviceEntryInteractor#isUnlocked` remains
     * `true` even when the lockscreen is showing and still needs to be dismissed by the user to
     * proceed.
     */
    suspend fun getAuthenticationMethod(): DomainLayerAuthenticationMethodModel {
        return repository.getAuthenticationMethod().toDomainLayer()
    }

    /**
     * Returns `true` if the device currently requires authentication before content can be viewed;
     * `false` if content can be displayed without unlocking first.
     */
    suspend fun isAuthenticationRequired(): Boolean {
        return !isUnlocked.value && getAuthenticationMethod().isSecure
    }

    /**
     * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
     * dismisses once the authentication challenge is completed. For example, completing a biometric
     * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
     * lock screen.
     */
    fun isBypassEnabled(): Boolean {
        return keyguardRepository.isBypassEnabled()
    }

    /**
     * Attempts to authenticate the user and unlock the device.
     *
@@ -312,7 +233,7 @@ constructor(

    /** Starts refreshing the throttling state every second. */
    private suspend fun startThrottlingCountdown() {
        cancelCountdown()
        cancelThrottlingCountdown()
        throttlingCountdownJob =
            applicationScope.launch {
                while (refreshThrottling() > 0) {
@@ -322,14 +243,14 @@ constructor(
    }

    /** Cancels any throttling state countdown started in [startThrottlingCountdown]. */
    private fun cancelCountdown() {
    private fun cancelThrottlingCountdown() {
        throttlingCountdownJob?.cancel()
        throttlingCountdownJob = null
    }

    /** Notifies that the currently-selected user has changed. */
    private suspend fun onSelectedUserChanged() {
        cancelCountdown()
        cancelThrottlingCountdown()
        if (refreshThrottling() > 0) {
            startThrottlingCountdown()
        }
@@ -378,7 +299,7 @@ constructor(
        DomainLayerAuthenticationMethodModel {
        return when (this) {
            is DataLayerAuthenticationMethodModel.None ->
                if (repository.isLockscreenEnabled()) {
                if (deviceEntryRepository.isInsecureLockscreenEnabled()) {
                    DomainLayerAuthenticationMethodModel.Swipe
                } else {
                    DomainLayerAuthenticationMethodModel.None
@@ -394,13 +315,10 @@ constructor(

/** Result of a user authentication attempt. */
enum class AuthenticationResult {
    /** Authentication succeeded and the device is now unlocked. */
    /** Authentication succeeded. */
    SUCCEEDED,
    /** Authentication failed and the device remains unlocked. */
    /** Authentication failed. */
    FAILED,
    /**
     * Authentication was not performed, e.g. due to insufficient input, and the device remains
     * unlocked.
     */
    /** Authentication was not performed, e.g. due to insufficient input. */
    SKIPPED,
}
+3 −1
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import com.android.systemui.classifier.FalsingClassifier
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
@@ -50,6 +51,7 @@ constructor(
    @Application private val applicationScope: CoroutineScope,
    @Application private val applicationContext: Context,
    private val repository: BouncerRepository,
    private val deviceEntryInteractor: DeviceEntryInteractor,
    private val authenticationInteractor: AuthenticationInteractor,
    private val sceneInteractor: SceneInteractor,
    flags: SceneContainerFlags,
@@ -144,7 +146,7 @@ constructor(
        message: String? = null,
    ) {
        applicationScope.launch {
            if (authenticationInteractor.isAuthenticationRequired()) {
            if (deviceEntryInteractor.isAuthenticationRequired()) {
                repository.setMessage(
                    message ?: promptMessage(authenticationInteractor.getAuthenticationMethod())
                )
+2 −0
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import com.android.systemui.controls.dagger.ControlsModule;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.SystemUser;
import com.android.systemui.demomode.dagger.DemoModeModule;
import com.android.systemui.deviceentry.DeviceEntryModule;
import com.android.systemui.display.DisplayModule;
import com.android.systemui.doze.dagger.DozeComponent;
import com.android.systemui.dreams.dagger.DreamModule;
@@ -173,6 +174,7 @@ import javax.inject.Named;
        ControlsModule.class,
        CoroutinesModule.class,
        DemoModeModule.class,
        DeviceEntryModule.class,
        DisableFlagsModule.class,
        DisplayModule.class,
        DreamModule.class,
Loading