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

Commit ff0c50c2 authored by Beverly's avatar Beverly Committed by Beverly Tai
Browse files

Add UdfpsTouchOverlayViewModels

Introduce UdfpsTouchOverlayViewModels and bind
the appropriate ViewModel to the UdfpsTouchOverlay
depending on who requested UDFPS.

For DeviceEntry, SysUI continues to requests UDFPS even
when isn't desirable to show the UI and allow UDFPS to be accepted.
This is to avoid frequent IPCs on the lockscreen, so the experience is
smooth & quick. For example, users can't use UDFPS when the camera app
is occluding the lockscreen; however, once the user navigates out of the
camera app (back) or taps on an affordance that requires authentication,
SysUI should immediately show the UI for UDFPS and allow authentication
without any delay.

Therefore, instead of stopping fingerprint listening
when an occluded app is on the keyguard, device entry relies on the
DeviceEntryUdfpsTouchOverlayViewModel to block touches on the
UdfpsTouchOverlay from being sent to FingerprintManager and relies on
the DeviceEntryIconViewModel to stop visually showing the device entry
(UDFPS) icon when UDFPS shouldn't be allowed.

For non-device entry cases, SysUI is not responsible for the visual UI;
however, SysUI is still responsible for forwarding UDFPS touches to the
FingerprintManager. In these cases, DefaultUdfpsTouchOverlayViewModel is
used to determine whether SysUI is in a state where it should send
UDFPS touches to the FingerprintManager. Currently, SysUI won't send
these touches if the shade is partially or fully expanded OR if there's
a sysui dialog requesting to hide affordances on the display.

This CL also updates a lot of TestDependencies to easily add a test for
DeviceEntryUdfpsTouchOverlayViewModel.

Bug: 305234447
Flag: ACONFIG com.android.systemui.device_entry_udfps_refactor DEVELOPMENT
Test: atest UdfpsControllerTest UdfpsControllerOverlayTest
Test: atest DefaultUdfpsTouchOverlayViewModelTest DeviceEntryUdfpsTouchOverlayViewModelTest
Change-Id: I4d9b22e9eb241f534640deea5a3bc99f2b854d9e
parent 3fc09fe3
Loading
Loading
Loading
Loading
+132 −133
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
import android.testing.TestableLooper.RunWithLooper
import android.view.LayoutInflater
@@ -40,6 +39,8 @@ import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.biometrics.ui.viewmodel.DefaultUdfpsTouchOverlayViewModel
import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
@@ -67,8 +68,8 @@ import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit

private const val REQUEST_ID = 2L

@@ -86,7 +87,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {

    @JvmField @Rule var rule = MockitoJUnit.rule()

    @Mock private lateinit var fingerprintManager: FingerprintManager
    @Mock private lateinit var inflater: LayoutInflater
    @Mock private lateinit var windowManager: WindowManager
    @Mock private lateinit var accessibilityManager: AccessibilityManager
@@ -98,8 +98,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
    @Mock private lateinit var transitionController: LockscreenShadeTransitionController
    @Mock private lateinit var configurationController: ConfigurationController
    @Mock private lateinit var keyguardStateController: KeyguardStateController
    @Mock private lateinit var unlockedScreenOffAnimationController:
            UnlockedScreenOffAnimationController
    @Mock
    private lateinit var unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController
    @Mock private lateinit var udfpsDisplayMode: UdfpsDisplayModeProvider
    @Mock private lateinit var secureSettings: SecureSettings
    @Mock private lateinit var controllerCallback: IUdfpsOverlayControllerCallback
@@ -110,8 +110,12 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
    @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
    @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
    @Mock private lateinit var udfpsKeyguardAccessibilityDelegate:
            UdfpsKeyguardAccessibilityDelegate
    @Mock
    private lateinit var deviceEntryUdfpsTouchOverlayViewModel:
        DeviceEntryUdfpsTouchOverlayViewModel
    @Mock private lateinit var defaultUdfpsTouchOverlayViewModel: DefaultUdfpsTouchOverlayViewModel
    @Mock
    private lateinit var udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate
    @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
    @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>

@@ -121,8 +125,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {

    @Before
    fun setup() {
        whenever(inflater.inflate(R.layout.udfps_view, null, false))
                .thenReturn(udfpsView)
        whenever(inflater.inflate(R.layout.udfps_view, null, false)).thenReturn(udfpsView)
        whenever(inflater.inflate(R.layout.udfps_bp_view, null))
            .thenReturn(mock(UdfpsBpView::class.java))
        whenever(inflater.inflate(R.layout.udfps_keyguard_view_legacy, null))
@@ -142,7 +145,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
        } else {
            mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
        }
        controllerOverlay = UdfpsControllerOverlay(
        controllerOverlay =
            UdfpsControllerOverlay(
                context,
                inflater,
                windowManager,
@@ -168,27 +172,29 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
                udfpsKeyguardAccessibilityDelegate,
                keyguardTransitionInteractor,
                mSelectedUserInteractor,
                { deviceEntryUdfpsTouchOverlayViewModel },
                { defaultUdfpsTouchOverlayViewModel },
            )
        block()
    }

    @Test
    fun showUdfpsOverlay_bp() = withReason(REASON_AUTH_BP) { showUdfpsOverlay() }
    @Test fun showUdfpsOverlay_bp() = withReason(REASON_AUTH_BP) { showUdfpsOverlay() }

    @Test
    fun showUdfpsOverlay_keyguard() = withReason(REASON_AUTH_KEYGUARD) {
    fun showUdfpsOverlay_keyguard() =
        withReason(REASON_AUTH_KEYGUARD) {
            showUdfpsOverlay()
            verify(mUdfpsKeyguardViewLegacy).updateSensorLocation(eq(overlayParams.sensorBounds))
        }

    @Test
    fun showUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { showUdfpsOverlay() }
    @Test fun showUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { showUdfpsOverlay() }

    private fun withRotation(@Rotation rotation: Int, block: () -> Unit) {
        // Sensor that's in the top left corner of the display in natural orientation.
        val sensorBounds = Rect(0, 0, SENSOR_WIDTH, SENSOR_HEIGHT)
        val overlayBounds = Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)
        overlayParams = UdfpsOverlayParams(
        overlayParams =
            UdfpsOverlayParams(
                sensorBounds,
                overlayBounds,
                DISPLAY_WIDTH,
@@ -200,13 +206,12 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
    }

    @Test
    fun showUdfpsOverlay_withRotation0() = withRotation(Surface.ROTATION_0) {
    fun showUdfpsOverlay_withRotation0() =
        withRotation(Surface.ROTATION_0) {
            withReason(REASON_AUTH_BP) {
                controllerOverlay.show(udfpsController, overlayParams)
            verify(windowManager).addView(
                    eq(controllerOverlay.getTouchOverlay()),
                    layoutParamsCaptor.capture()
            )
                verify(windowManager)
                    .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture())

                // ROTATION_0 is the native orientation. Sensor should stay in the top left corner.
                val lp = layoutParamsCaptor.value
@@ -218,13 +223,12 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
        }

    @Test
    fun showUdfpsOverlay_withRotation180() = withRotation(Surface.ROTATION_180) {
    fun showUdfpsOverlay_withRotation180() =
        withRotation(Surface.ROTATION_180) {
            withReason(REASON_AUTH_BP) {
                controllerOverlay.show(udfpsController, overlayParams)
            verify(windowManager).addView(
                    eq(controllerOverlay.getTouchOverlay()),
                    layoutParamsCaptor.capture()
            )
                verify(windowManager)
                    .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture())

                // ROTATION_180 is not supported. Sensor should stay in the top left corner.
                val lp = layoutParamsCaptor.value
@@ -236,13 +240,12 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
        }

    @Test
    fun showUdfpsOverlay_withRotation90() = withRotation(Surface.ROTATION_90) {
    fun showUdfpsOverlay_withRotation90() =
        withRotation(Surface.ROTATION_90) {
            withReason(REASON_AUTH_BP) {
                controllerOverlay.show(udfpsController, overlayParams)
            verify(windowManager).addView(
                    eq(controllerOverlay.getTouchOverlay()),
                    layoutParamsCaptor.capture()
            )
                verify(windowManager)
                    .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture())

                // Sensor should be in the bottom left corner in ROTATION_90.
                val lp = layoutParamsCaptor.value
@@ -254,13 +257,12 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
        }

    @Test
    fun showUdfpsOverlay_withRotation270() = withRotation(Surface.ROTATION_270) {
    fun showUdfpsOverlay_withRotation270() =
        withRotation(Surface.ROTATION_270) {
            withReason(REASON_AUTH_BP) {
                controllerOverlay.show(udfpsController, overlayParams)
            verify(windowManager).addView(
                    eq(controllerOverlay.getTouchOverlay()),
                    layoutParamsCaptor.capture()
            )
                verify(windowManager)
                    .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture())

                // Sensor should be in the top right corner in ROTATION_270.
                val lp = layoutParamsCaptor.value
@@ -285,17 +287,13 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
        assertThat(controllerOverlay.getTouchOverlay()).isNotNull()
    }

    @Test
    fun hideUdfpsOverlay_bp() = withReason(REASON_AUTH_BP) { hideUdfpsOverlay() }
    @Test fun hideUdfpsOverlay_bp() = withReason(REASON_AUTH_BP) { hideUdfpsOverlay() }

    @Test
    fun hideUdfpsOverlay_keyguard() = withReason(REASON_AUTH_KEYGUARD) { hideUdfpsOverlay() }
    @Test fun hideUdfpsOverlay_keyguard() = withReason(REASON_AUTH_KEYGUARD) { hideUdfpsOverlay() }

    @Test
    fun hideUdfpsOverlay_settings() = withReason(REASON_AUTH_SETTINGS) { hideUdfpsOverlay() }
    @Test fun hideUdfpsOverlay_settings() = withReason(REASON_AUTH_SETTINGS) { hideUdfpsOverlay() }

    @Test
    fun hideUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { hideUdfpsOverlay() }
    @Test fun hideUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { hideUdfpsOverlay() }

    private fun hideUdfpsOverlay() {
        val didShow = controllerOverlay.show(udfpsController, overlayParams)
@@ -313,24 +311,25 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
    }

    @Test
    fun canNotHide() = withReason(REASON_AUTH_BP) {
        assertThat(controllerOverlay.hide()).isFalse()
    }
    fun canNotHide() = withReason(REASON_AUTH_BP) { assertThat(controllerOverlay.hide()).isFalse() }

    @Test
    fun canNotReshow() = withReason(REASON_AUTH_BP) {
    fun canNotReshow() =
        withReason(REASON_AUTH_BP) {
            assertThat(controllerOverlay.show(udfpsController, overlayParams)).isTrue()
            assertThat(controllerOverlay.show(udfpsController, overlayParams)).isFalse()
        }

    @Test
    fun cancels() = withReason(REASON_AUTH_BP) {
    fun cancels() =
        withReason(REASON_AUTH_BP) {
            controllerOverlay.cancel()
            verify(controllerCallback).onUserCanceled()
        }

    @Test
    fun unconfigureDisplayOnHide() = withReason(REASON_AUTH_BP) {
    fun unconfigureDisplayOnHide() =
        withReason(REASON_AUTH_BP) {
            whenever(udfpsView.isDisplayConfigured).thenReturn(true)

            controllerOverlay.show(udfpsController, overlayParams)
@@ -339,22 +338,22 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
        }

    @Test
    fun matchesRequestIds() = withReason(REASON_AUTH_BP) {
    fun matchesRequestIds() =
        withReason(REASON_AUTH_BP) {
            assertThat(controllerOverlay.matchesRequestId(REQUEST_ID)).isTrue()
            assertThat(controllerOverlay.matchesRequestId(REQUEST_ID + 1)).isFalse()
        }

    @Test
    fun smallOverlayOnEnrollmentWithA11y() = withRotation(Surface.ROTATION_0) {
    fun smallOverlayOnEnrollmentWithA11y() =
        withRotation(Surface.ROTATION_0) {
            withReason(REASON_ENROLL_ENROLLING) {
                // When a11y enabled during enrollment
                whenever(accessibilityManager.isTouchExplorationEnabled).thenReturn(true)

                controllerOverlay.show(udfpsController, overlayParams)
            verify(windowManager).addView(
                    eq(controllerOverlay.getTouchOverlay()),
                    layoutParamsCaptor.capture()
            )
                verify(windowManager)
                    .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture())

                // Layout params should use sensor bounds
                val lp = layoutParamsCaptor.value
+12 −1
Original line number Diff line number Diff line
@@ -81,6 +81,8 @@ import com.android.systemui.biometrics.udfps.InteractionEvent;
import com.android.systemui.biometrics.udfps.NormalizedTouchData;
import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor;
import com.android.systemui.biometrics.udfps.TouchProcessorResult;
import com.android.systemui.biometrics.ui.viewmodel.DefaultUdfpsTouchOverlayViewModel;
import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.dump.DumpManager;
@@ -106,6 +108,8 @@ import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.time.SystemClock;

import dagger.Lazy;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -241,6 +245,10 @@ public class UdfpsControllerTest extends SysuiTestCase {
    private FpsUnlockTracker mFpsUnlockTracker;
    @Mock
    private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
    @Mock
    private Lazy<DeviceEntryUdfpsTouchOverlayViewModel> mDeviceEntryUdfpsTouchOverlayViewModel;
    @Mock
    private Lazy<DefaultUdfpsTouchOverlayViewModel> mDefaultUdfpsTouchOverlayViewModel;

    @Before
    public void setUp() {
@@ -334,7 +342,10 @@ public class UdfpsControllerTest extends SysuiTestCase {
                mUdfpsKeyguardViewModels,
                mSelectedUserInteractor,
                mFpsUnlockTracker,
                mKeyguardTransitionInteractor
                mKeyguardTransitionInteractor,
                mDeviceEntryUdfpsTouchOverlayViewModel,
                mDefaultUdfpsTouchOverlayViewModel

        );
        verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
        mOverlayController = mOverlayCaptor.getValue();
+2 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.when;
import android.content.Context;

import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
@@ -94,6 +95,7 @@ public class UdfpsKeyguardViewLegacyControllerBaseTest extends SysuiTestCase {
    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
        when(mView.getContext()).thenReturn(mResourceContext);
        when(mResourceContext.getString(anyInt())).thenReturn("test string");
        when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false);
+2 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import kotlinx.coroutines.test.TestScope
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -166,6 +167,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() {
    }

    @Test
    @Ignore("b/287599719")
    fun canShowAlternateBouncerForFingerprint_rearFps() {
        mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
        initializeUnderTest()
+23 −7
Original line number Diff line number Diff line
@@ -74,6 +74,8 @@ import com.android.systemui.biometrics.udfps.NormalizedTouchData;
import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor;
import com.android.systemui.biometrics.udfps.TouchProcessor;
import com.android.systemui.biometrics.udfps.TouchProcessorResult;
import com.android.systemui.biometrics.ui.viewmodel.DefaultUdfpsTouchOverlayViewModel;
import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
@@ -102,6 +104,8 @@ import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
import com.android.systemui.util.time.SystemClock;

import dagger.Lazy;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
@@ -164,6 +168,10 @@ public class UdfpsController implements DozeReceiver, Dumpable {
    @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
    @Nullable private final TouchProcessor mTouchProcessor;
    @NonNull private final SessionTracker mSessionTracker;
    @NonNull private final Lazy<DeviceEntryUdfpsTouchOverlayViewModel>
            mDeviceEntryUdfpsTouchOverlayViewModel;
    @NonNull private final Lazy<DefaultUdfpsTouchOverlayViewModel>
            mDefaultUdfpsTouchOverlayViewModel;
    @NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor;
    @NonNull private final InputManager mInputManager;
    @NonNull private final UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
@@ -284,7 +292,9 @@ public class UdfpsController implements DozeReceiver, Dumpable {
                        mAlternateBouncerInteractor,
                        mUdfpsKeyguardAccessibilityDelegate,
                        mKeyguardTransitionInteractor,
                        mSelectedUserInteractor
                        mSelectedUserInteractor,
                        mDeviceEntryUdfpsTouchOverlayViewModel,
                        mDefaultUdfpsTouchOverlayViewModel
                    )));
        }

@@ -501,12 +511,14 @@ public class UdfpsController implements DozeReceiver, Dumpable {
                    + mOverlay.getRequestId());
            return false;
        }
        if (!DeviceEntryUdfpsRefactor.isEnabled()) {
            if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f
                    && !mAlternateBouncerInteractor.isVisibleState())
                    || mPrimaryBouncerInteractor.isInTransit()) {
                Log.w(TAG, "ignoring touch due to qsDragProcess or primaryBouncerInteractor");
                return false;
            }
        }
        if (event.getAction() == MotionEvent.ACTION_DOWN
                || event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
            // Reset on ACTION_DOWN, start of new gesture
@@ -657,7 +669,9 @@ public class UdfpsController implements DozeReceiver, Dumpable {
            @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider,
            @NonNull SelectedUserInteractor selectedUserInteractor,
            @NonNull FpsUnlockTracker fpsUnlockTracker,
            @NonNull KeyguardTransitionInteractor keyguardTransitionInteractor) {
            @NonNull KeyguardTransitionInteractor keyguardTransitionInteractor,
            Lazy<DeviceEntryUdfpsTouchOverlayViewModel> deviceEntryUdfpsTouchOverlayViewModel,
            Lazy<DefaultUdfpsTouchOverlayViewModel> defaultUdfpsTouchOverlayViewModel) {
        mContext = context;
        mExecution = execution;
        mVibrator = vibrator;
@@ -706,6 +720,8 @@ public class UdfpsController implements DozeReceiver, Dumpable {

        mTouchProcessor = singlePointerTouchProcessor;
        mSessionTracker = sessionTracker;
        mDeviceEntryUdfpsTouchOverlayViewModel = deviceEntryUdfpsTouchOverlayViewModel;
        mDefaultUdfpsTouchOverlayViewModel = defaultUdfpsTouchOverlayViewModel;

        mDumpManager.registerDumpable(TAG, this);

Loading