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

Commit ef6c53ba authored by Grace Cheng's avatar Grace Cheng
Browse files

Secure lock device implementation for legacy keyguard

KeyguardSecurityContainerController updates: does not dismiss after
successful primary auth, updates keyguard view shown for primary ->
biometric authentication transition, biometric -> lockscreen transition,
biometric -> gone transition

StatusBarKeyguardViewManager updates: resets keyguard and hides bouncer
on secure lock device biometric auth interruption, notifies
PrimaryBouncerInteractor and KeyguardDismissTransitionInteractor when
secure lock device is fully unlocked & ready to dismiss, notifies
SecureLockDeviceInteractor when gone transition finishes, and does not
notify PrimaryBouncerInteractor on biometric authentication (this is
handled by KeyguardBouncerViewBinder)

Updates KeyguardSecurityViewFlipperController to return the new view for
the SecureLockDeviceBiometricAuth security mode

Updates KeyguardSecurityModel to return
SecurityMode.SecureLockDeviceBiometricAuth when secure lock device
biometric authentication is being shown or pending dismissal

Updates SecureLockDeviceInteractor to track unlock progress during
secure lock device, and reset UI state, auth flags, and face auth
request status in response to biometric auth being requested,
interrupted, or completed

Updates KeyguardService to not re-enable keyguard after secure lock
device unlock is complete

Updates KeyguardBouncerViewModel/ViewBinder to handle showing and hiding
the biometric authentication view on biometric authentication requested,
interrrupted, and completion

Flag: android.security.secure_lock_device
Bug: 401645997
Fixes: 427071518
Test: atest KeyguardSecurityContainerControllerTest
Test: atest KeyguardBouncerViewModelTest
Test: atest StatusBarKeyguardViewManagerTest
Change-Id: Ia4375434b09efc68c5502e6008462d784ace7596
parent 6a7309a3
Loading
Loading
Loading
Loading
+80 −1
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.keyguard
import android.app.admin.DevicePolicyManager
import android.content.res.Configuration
import android.media.AudioManager
import android.platform.test.annotations.EnableFlags
import android.security.Flags.FLAG_SECURE_LOCK_DEVICE
import android.telephony.TelephonyManager
import android.testing.TestableLooper.RunWithLooper
import android.testing.TestableResources
@@ -73,6 +75,8 @@ import com.android.systemui.scene.shared.model.FakeSceneDataSource
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.securelockdevice.data.repository.fakeSecureLockDeviceRepository
import com.android.systemui.securelockdevice.domain.interactor.secureLockDeviceInteractor
import com.android.systemui.shade.domain.interactor.enableSingleShade
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.DevicePostureController
@@ -94,6 +98,7 @@ import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.wrapper.LockPatternCheckerWrapper
import com.android.systemui.window.domain.interactor.windowRootViewBlurInteractor
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -166,6 +171,9 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
    @Mock private lateinit var bouncerInteractor: BouncerInteractor
    @Mock private lateinit var lockPatternChecker: LockPatternCheckerWrapper

    @Captor
    private lateinit var keyguardUpdateMonitorCallbackCaptor:
        ArgumentCaptor<KeyguardUpdateMonitorCallback>
    @Captor
    private lateinit var swipeListenerArgumentCaptor:
        ArgumentCaptor<KeyguardSecurityContainer.SwipeListener>
@@ -196,7 +204,6 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
        testableResources.resources.configuration.orientation = Configuration.ORIENTATION_UNDEFINED
        whenever(view.context).thenReturn(mContext)
        whenever(view.resources).thenReturn(testableResources.resources)

        val lp = FrameLayout.LayoutParams(/* width= */ 0, /* height= */ 0)
        lp.gravity = 0
        whenever(view.layoutParams).thenReturn(lp)
@@ -297,6 +304,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
                { deviceEntryInteractor },
                { kosmos.windowRootViewBlurInteractor },
                { bouncerInteractor },
                { kosmos.secureLockDeviceInteractor },
            )
    }

@@ -686,6 +694,37 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
        verify(viewFlipperController).getSecurityView(eq(SecurityMode.SimPin), any(), any())
    }

    @Test
    @DisableSceneContainer
    @EnableFlags(FLAG_SECURE_LOCK_DEVICE)
    fun keyguardDoesNotDismiss_onPrimaryAuthSuccess_ifSecureLockDeviceEnabled() =
        kosmos.testScope.runTest {
            val isSecureLockDeviceEnabled by
                collectLastValue(kosmos.secureLockDeviceInteractor.isSecureLockDeviceEnabled)

            kosmos.fakeSecureLockDeviceRepository.onSecureLockDeviceEnabled()
            runCurrent()

            assertThat(isSecureLockDeviceEnabled).isTrue()

            // GIVEN current security mode has been set to PIN
            underTest.showSecurityScreen(SecurityMode.PIN)

            // WHEN a primary auth success requests to dismiss the security screen
            kosmos.fakeSecureLockDeviceRepository.onSuccessfulPrimaryAuth()
            val keyguardDone =
                underTest.showNextSecurityScreenOrFinish(
                    /* authenticated= */ true,
                    TARGET_USER_ID,
                    /* bypassSecondaryLockScreen= */ true,
                    SecurityMode.PIN,
                )
            runCurrent()

            // THEN no action has happened, which will not dismiss the security screens
            assertThat(keyguardDone).isEqualTo(false)
        }

    @Test
    @DisableSceneContainer
    fun onSwipeUp_forwardsItToFaceAuthInteractor() {
@@ -882,6 +921,38 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
        verify(view).updateSecurityViewFlipper()
    }

    @Test
    @EnableFlags(FLAG_SECURE_LOCK_DEVICE)
    @DisableSceneContainer
    fun reinflateViewFlipper_onSecureLockDeviceBiometricAuthViewShownOrInterrupted() {
        // On shown
        val onViewInflatedCallback = KeyguardSecurityViewFlipperController.OnViewInflatedCallback {}
        underTest.showSecureLockDeviceView(onViewInflatedCallback)
        verify(viewFlipperController).clearViews()
        verify(viewFlipperController)
            .getSecurityView(
                eq(SecurityMode.SecureLockDeviceBiometricAuth),
                any(),
                onViewInflatedCallbackArgumentCaptor.capture(),
            )
        onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
        verify(view).updateSecurityViewFlipper()

        clearInvocations(view)
        clearInvocations(viewFlipperController)

        // On hidden (e.g. back gesture, dozing, biometric lockout, etc)
        kosmos.fakeSecureLockDeviceRepository.setRequiresPrimaryAuthForSecureLockDevice(true)

        underTest.onSecureLockDeviceBiometricAuthInterrupted(onViewInflatedCallback)
        verify(viewFlipperController).clearViews()
        verify(viewFlipperController)
            .getSecurityView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture())
        onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
        verify(view).updateSecurityViewFlipper()
        verify(viewMediatorCallback).resetKeyguard()
    }

    @Test
    @DisableSceneContainer
    fun setExpansion_setsAlpha() {
@@ -1113,6 +1184,14 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
            )
    }

    private val registeredKeyguardUpdateMonitorCallback: KeyguardUpdateMonitorCallback
        get() {
            underTest.onViewAttached()
            verify(keyguardUpdateMonitor)
                .registerCallback(keyguardUpdateMonitorCallbackCaptor.capture())
            return keyguardUpdateMonitorCallbackCaptor.value
        }

    private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener
        get() {
            underTest.onViewAttached()
+11 −1
Original line number Diff line number Diff line
@@ -33,7 +33,10 @@ import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.securelockdevice.domain.interactor.secureLockDeviceInteractor
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
@@ -52,6 +55,7 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardBouncerViewModelTest : SysuiTestCase() {
    private val kosmos = testKosmos()

    @Mock lateinit var bouncerView: BouncerView
    @Mock private lateinit var keyguardStateController: KeyguardStateController
@@ -89,7 +93,13 @@ class KeyguardBouncerViewModelTest : SysuiTestCase() {
                mSelectedUserInteractor,
                faceAuthInteractor,
            )
        underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
        underTest =
            KeyguardBouncerViewModel(
                bouncerView,
                bouncerInteractor,
                { kosmos.secureLockDeviceInteractor },
                { kosmos.keyguardTransitionInteractor },
            )
    }

    @Test
+72 −3
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.keyguard;
import static android.app.StatusBarManager.SESSION_KEYGUARD;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.security.Flags.secureLockDevice;

import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISSIBLE_KEYGUARD;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
@@ -29,6 +30,7 @@ import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_SIM
import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_PRIMARY;
import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER;
import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_WORK_PROFILE;
import static com.android.keyguard.KeyguardSecurityModel.SecurityMode.SecureLockDeviceBiometricAuth;
import static com.android.keyguard.KeyguardSecurityModel.SecurityMode.SimPin;
import static com.android.keyguard.KeyguardSecurityModel.SecurityMode.SimPuk;
import static com.android.systemui.DejankUtils.whitelistIpcs;
@@ -90,6 +92,7 @@ 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.SceneContainerFlag;
import com.android.systemui.securelockdevice.domain.interactor.SecureLockDeviceInteractor;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -122,6 +125,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
    private static final String TAG = "KeyguardSecurityView";

    private final AdminSecondaryLockScreenController mAdminSecondaryLockScreenController;
    private final Lazy<SecureLockDeviceInteractor> mSecureLockDeviceInteractor;
    private final LockPatternUtils mLockPatternUtils;
    private final KeyguardUpdateMonitor mUpdateMonitor;
    private final KeyguardSecurityModel mSecurityModel;
@@ -430,8 +434,15 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard

                @Override
                public void onDevicePolicyManagerStateChanged() {
                    if (secureLockDevice() && (mSecureLockDeviceInteractor.get()
                            .isSecureLockDeviceEnabled().getValue() || mSecureLockDeviceInteractor
                            .get().isAuthenticatedButPendingDismissal().getValue())) {
                        Log.d(TAG, "Secure lock device is enabled or keyguard dismissal from"
                                + " secure lock device is pending, do not reshow security screen.");
                    } else {
                        showPrimarySecurityScreen(false);
                    }
                }
            };
    private final SelectedUserInteractor mSelectedUserInteractor;
    private final Provider<DeviceEntryInteractor> mDeviceEntryInteractor;
@@ -477,7 +488,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
            @Background Executor bgExecutor,
            Provider<DeviceEntryInteractor> deviceEntryInteractor,
            Lazy<WindowRootViewBlurInteractor> rootViewBlurInteractorProvider,
            Lazy<BouncerInteractor> bouncerInteractor
            Lazy<BouncerInteractor> bouncerInteractor,
            Lazy<SecureLockDeviceInteractor> secureLockDeviceInteractor
    ) {
        super(view);
        mRootViewBlurInteractor = rootViewBlurInteractorProvider;
@@ -491,6 +503,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
        mSecurityViewFlipperController = securityViewFlipperController;
        mAdminSecondaryLockScreenController = adminSecondaryLockScreenControllerFactory.create(
                mKeyguardSecurityCallback);
        mSecureLockDeviceInteractor = secureLockDeviceInteractor;
        mConfigurationController = configurationController;
        mLastOrientation = getResources().getConfiguration().orientation;
        mFalsingCollector = falsingCollector;
@@ -968,6 +981,17 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
            }
        }

        // If secure lock device requires second-factor biometric auth after the bouncer auth.
        if (finish && secureLockDevice() && mSecureLockDeviceInteractor.get()
                .isSecureLockDeviceEnabled().getValue() && !mSecureLockDeviceInteractor.get()
                .isAuthenticatedButPendingDismissal().getValue()
        ) {
            Log.d(TAG, "Secure lock device biometric auth required after primary auth");
            // SecureLockDeviceBiometricAuth screen will be shown by KeyguardBouncerViewBinder
            // collection of strong biometric auth flag
            return false;
        }

        // Check for device admin specified additional security measures.
        if (finish && !bypassSecondaryLockScreen) {
            Intent secondaryLockscreenIntent =
@@ -1125,6 +1149,10 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
    @VisibleForTesting
    void showSecurityScreen(SecurityMode securityMode) {
        if (DEBUG) Log.d(TAG, "showSecurityScreen(" + securityMode + ")");
        if (secureLockDevice() && SceneContainerFlag.isEnabled()
                && securityMode == SecureLockDeviceBiometricAuth) {
            return;
        }

        if (securityMode == SecurityMode.Invalid || securityMode == mCurrentSecurityMode) {
            return;
@@ -1133,7 +1161,6 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
        getCurrentSecurityController(oldView -> oldView.onPause());

        mCurrentSecurityMode = securityMode;

        getCurrentSecurityController(
                newView -> {
                    newView.onResume(KeyguardSecurityView.VIEW_REVEALED);
@@ -1308,6 +1335,40 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
            });
    }

    /**
     * Resets the view flipper and reinflates the child view shown during secure lock device
     * biometric authentication.
     * @param onViewInflatedListener provides callback to be performed after the view is inflated
     */
    public void showSecureLockDeviceView(
            KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedListener) {
        mSecurityViewFlipperController.clearViews();
        mCurrentSecurityMode = SecureLockDeviceBiometricAuth;
        mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode,
                mKeyguardSecurityCallback, (controller) -> {
                    mView.updateSecurityViewFlipper();
                    onViewInflatedListener.onViewInflated(controller);
                });
    }

    /**
     * Resets the view flipper and reinflates the child view shown during secure lock device
     * biometric authentication.
     * @param onViewInflatedListener provides callback to be performed after the view is inflated
     */
    public void onSecureLockDeviceBiometricAuthInterrupted(
            KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedListener) {
        SecurityMode newSecurityMode =
                mSecurityModel.getSecurityMode(mSelectedUserInteractor.getSelectedUserId());
        mSecurityViewFlipperController.clearViews();
        mSecurityViewFlipperController.getSecurityView(newSecurityMode,
                mKeyguardSecurityCallback, (controller) -> {
                    mView.updateSecurityViewFlipper();
                    onViewInflatedListener.onViewInflated(controller);
                });
        mViewMediatorCallback.resetKeyguard();
    }

    /**
     * Fades and translates in/out the security screen.
     * Fades in as expansion approaches 0.
@@ -1329,4 +1390,12 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
    public boolean isLandscapeOrientation() {
        return mLastOrientation == Configuration.ORIENTATION_LANDSCAPE;
    }

    /**
     * Called to reset the security mode after device entry from unlock in secure lock device mode.
     */
    public void onSecureLockDeviceUnlock() {
        final int selectedUserId = mSelectedUserInteractor.getSelectedUserId();
        mCurrentSecurityMode = mSecurityModel.getSecurityMode(selectedUserId);
    }
}
+11 −1
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ public class KeyguardSecurityModel {

    private final boolean mIsPukScreenAvailable;
    private boolean mRequiresBiometricForSecureLockDevice;
    private boolean mAuthenticatedInSecureLockDevice;

    private final LockPatternUtils mLockPatternUtils;
    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -83,6 +84,10 @@ public class KeyguardSecurityModel {
                            .getRequiresStrongBiometricAuthForSecureLockDevice(),
                    this::onRequiresStrongBiometricAuthForSecureLockDevice
            );
            mJavaAdapter.get().alwaysCollectFlow(
                    mSecureLockDeviceInteractor.get().isAuthenticatedButPendingDismissal(),
                    this::onSecureLockDeviceAuthenticationComplete
            );
        }
    }

@@ -90,8 +95,13 @@ public class KeyguardSecurityModel {
        mRequiresBiometricForSecureLockDevice = requiresBiometricAuth;
    }

    private void onSecureLockDeviceAuthenticationComplete(boolean authenticatedInSecureLockDevice) {
        mAuthenticatedInSecureLockDevice = authenticatedInSecureLockDevice;
    }

    public SecurityMode getSecurityMode(int userId) {
        if (secureLockDevice() && mRequiresBiometricForSecureLockDevice) {
        if (secureLockDevice()
                && (mRequiresBiometricForSecureLockDevice || mAuthenticatedInSecureLockDevice)) {
            return SecurityMode.SecureLockDeviceBiometricAuth;
        }

+20 −18
Original line number Diff line number Diff line
@@ -170,27 +170,29 @@ public class KeyguardSecurityViewFlipperController

    private int getLayoutIdFor(SecurityMode securityMode) {
        // TODO (b/297863911, b/297864907) - implement motion layout for other bouncers
        switch (securityMode) {
            case Pattern: return R.layout.keyguard_pattern_motion_layout;
            case PIN: return R.layout.keyguard_pin_motion_layout;
            case Password: return R.layout.keyguard_password_motion_layout;
            case SimPin: return R.layout.keyguard_sim_pin_view;
            case SimPuk: return R.layout.keyguard_sim_puk_view;
            default:
                return 0;
        }
        return switch (securityMode) {
            case SecureLockDeviceBiometricAuth ->
                    R.layout.keyguard_secure_lock_device_biometric_auth_view;
            case Pattern -> R.layout.keyguard_pattern_motion_layout;
            case PIN -> R.layout.keyguard_pin_motion_layout;
            case Password -> R.layout.keyguard_password_motion_layout;
            case SimPin -> R.layout.keyguard_sim_pin_view;
            case SimPuk -> R.layout.keyguard_sim_puk_view;
            default -> 0;
        };
    }

    private int getLegacyLayoutIdFor(SecurityMode securityMode) {
        switch (securityMode) {
            case Pattern: return R.layout.keyguard_pattern_view;
            case PIN: return R.layout.keyguard_pin_view;
            case Password: return R.layout.keyguard_password_view;
            case SimPin: return R.layout.keyguard_sim_pin_view;
            case SimPuk: return R.layout.keyguard_sim_puk_view;
            default:
                return 0;
        }
        return switch (securityMode) {
            case SecureLockDeviceBiometricAuth ->
                    R.layout.keyguard_secure_lock_device_biometric_auth_view;
            case Pattern -> R.layout.keyguard_pattern_view;
            case PIN -> R.layout.keyguard_pin_view;
            case Password -> R.layout.keyguard_password_view;
            case SimPin -> R.layout.keyguard_sim_pin_view;
            case SimPuk -> R.layout.keyguard_sim_puk_view;
            default -> 0;
        };
    }

    /** Updates the keyguard view's constraints (single or split constraints).
Loading