From a439c288210e0342087f231297881ccda10f2bd6 Mon Sep 17 00:00:00 2001 From: Beverly Date: Fri, 10 Mar 2023 16:28:51 +0000 Subject: [PATCH] Fix AlternateBouncer touch handling - Only handle altBouncer touches if up & down received - If UDFPS is enrolled and new touch handling is enabled, the UdfpsController will forward touches to the StatusBarKeyguardViewManager to handle alternateBouncer touches. Test: atest StatusBarKeyguardViewManagerTest NotificationShadeWindowViewTest UdfpsOverlayInteractorTest NotificationShadeWindowViewControllerTest Test: Use the alternateBouncer from an occluding app like the camera (double press power button from LS) Fixes: 273532029 Bug: 270285915 Bug: 272744555 Change-Id: I57bab58a47b9227fc13876edd3f6af22a840c147 --- .../systemui/biometrics/AuthController.java | 2 +- .../systemui/biometrics/UdfpsController.java | 1 - .../interactor/UdfpsOverlayInteractor.kt | 8 +- .../interactor/AlternateBouncerInteractor.kt | 2 + ...NotificationShadeWindowViewController.java | 24 +-- .../phone/StatusBarKeyguardViewManager.java | 55 +++++- .../interactor/UdfpsOverlayInteractorTest.kt | 10 +- ...tificationShadeWindowViewControllerTest.kt | 26 +-- .../shade/NotificationShadeWindowViewTest.kt | 14 +- .../StatusBarKeyguardViewManagerTest.java | 179 ++++++++++++++++-- 10 files changed, 245 insertions(+), 76 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 4aa985b509672..705fc8c1a8fd8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -823,7 +823,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, final Rect overlayBounds = new Rect( 0, /* left */ - mCachedDisplayInfo.getNaturalHeight() / 2, /* top */ + 0, /* top */ mCachedDisplayInfo.getNaturalWidth(), /* right */ mCachedDisplayInfo.getNaturalHeight() /* botom */); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 19bd86aba177b..e7ec3eb7e81a1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -607,7 +607,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { case UNCHANGED: if (!isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), true) && mActivePointerId == MotionEvent.INVALID_POINTER_ID - && event.getActionMasked() == MotionEvent.ACTION_DOWN && mAlternateBouncerInteractor.isVisibleState()) { // No pointer on sensor, forward to keyguard if alternateBouncer is visible mKeyguardViewManager.onTouch(event); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt index 92a7094c22bfa..9a0792ee79235 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt @@ -37,12 +37,12 @@ class UdfpsOverlayInteractor @Inject constructor(private val authController: AuthController, @Application scope: CoroutineScope) { - /** Whether a touch should be intercepted or allowed to pass to the UdfpsOverlay */ - fun canInterceptTouchInUdfpsBounds(ev: MotionEvent): Boolean { + /** Whether a touch is within the under-display fingerprint sensor area */ + fun isTouchWithinUdfpsArea(ev: MotionEvent): Boolean { val isUdfpsEnrolled = authController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser()) - val isWithinUdfpsOverlayBounds = + val isWithinOverlayBounds = udfpsOverlayParams.value.overlayBounds.contains(ev.rawX.toInt(), ev.rawY.toInt()) - return !isUdfpsEnrolled || !isWithinUdfpsOverlayBounds + return isUdfpsEnrolled && isWithinOverlayBounds } /** Returns the current udfpsOverlayParams */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt index eae40d61cdb61..d745a19e85499 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt @@ -46,6 +46,7 @@ constructor( var legacyAlternateBouncer: LegacyAlternateBouncer? = null var legacyAlternateBouncerVisibleTime: Long = NOT_VISIBLE + var receivedDownTouch = false val isVisible: Flow = bouncerRepository.alternateBouncerVisible /** @@ -79,6 +80,7 @@ constructor( * @return true if the alternate bouncer was newly hidden, else false. */ fun hide(): Boolean { + receivedDownTouch = false return if (isModernAlternateBouncerEnabled) { val wasAlternateBouncerVisible = isVisibleState() bouncerRepository.setAlternateVisible(false) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index ade743b3843eb..5f6f158277d72 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -37,14 +37,12 @@ import com.android.keyguard.AuthKeyguardMessageArea; import com.android.keyguard.LockIconViewController; import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.R; -import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.compose.ComposeFacade; import com.android.systemui.dock.DockManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; @@ -94,8 +92,6 @@ public class NotificationShadeWindowViewController { private final AmbientState mAmbientState; private final PulsingGestureListener mPulsingGestureListener; private final NotificationInsetsController mNotificationInsetsController; - private final AlternateBouncerInteractor mAlternateBouncerInteractor; - private final UdfpsOverlayInteractor mUdfpsOverlayInteractor; private final boolean mIsTrackpadCommonEnabled; private GestureDetector mPulsingWakeupGestureHandler; private View mBrightnessMirror; @@ -145,8 +141,6 @@ public class NotificationShadeWindowViewController { PulsingGestureListener pulsingGestureListener, KeyguardBouncerViewModel keyguardBouncerViewModel, KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory, - AlternateBouncerInteractor alternateBouncerInteractor, - UdfpsOverlayInteractor udfpsOverlayInteractor, KeyguardTransitionInteractor keyguardTransitionInteractor, PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, FeatureFlags featureFlags, @@ -170,8 +164,6 @@ public class NotificationShadeWindowViewController { mAmbientState = ambientState; mPulsingGestureListener = pulsingGestureListener; mNotificationInsetsController = notificationInsetsController; - mAlternateBouncerInteractor = alternateBouncerInteractor; - mUdfpsOverlayInteractor = udfpsOverlayInteractor; mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON); // This view is not part of the newly inflated expanded status bar. @@ -269,6 +261,9 @@ public class NotificationShadeWindowViewController { mFalsingCollector.onTouchEvent(ev); mPulsingWakeupGestureHandler.onTouchEvent(ev); + if (mStatusBarKeyguardViewManager.dispatchTouchEvent(ev)) { + return true; + } if (mBrightnessMirror != null && mBrightnessMirror.getVisibility() == View.VISIBLE) { // Disallow new pointers while the brightness mirror is visible. This is so that @@ -343,9 +338,10 @@ public class NotificationShadeWindowViewController { return true; } - if (mAlternateBouncerInteractor.isVisibleState()) { - // If using UDFPS, don't intercept touches that are within its overlay bounds - return mUdfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(ev); + if (mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(ev)) { + // Don't allow touches to proceed to underlying views if alternate + // bouncer is showing + return true; } if (mLockIconViewController.onInterceptTouchEvent(ev)) { @@ -381,10 +377,8 @@ public class NotificationShadeWindowViewController { handled = !mService.isPulsing(); } - if (mAlternateBouncerInteractor.isVisibleState()) { - // eat the touch - mStatusBarKeyguardViewManager.onTouch(ev); - handled = true; + if (mStatusBarKeyguardViewManager.onTouch(ev)) { + return true; } if ((mDragDownHelper.isDragDownEnabled() && !handled) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 69b683b9d054e..06d0758c90eb4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -54,6 +54,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.KeyguardViewController; import com.android.keyguard.ViewMediatorCallback; +import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dock.DockManager; import com.android.systemui.dreams.DreamOverlayStateController; @@ -282,6 +283,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb final Set mCallbacks = new HashSet<>(); private boolean mIsModernAlternateBouncerEnabled; private boolean mIsBackAnimationEnabled; + private final boolean mUdfpsNewTouchDetectionEnabled; + private final UdfpsOverlayInteractor mUdfpsOverlayInteractor; private OnDismissAction mAfterKeyguardGoneAction; private Runnable mKeyguardGoneCancelAction; @@ -336,7 +339,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor, PrimaryBouncerInteractor primaryBouncerInteractor, BouncerView primaryBouncerView, - AlternateBouncerInteractor alternateBouncerInteractor) { + AlternateBouncerInteractor alternateBouncerInteractor, + UdfpsOverlayInteractor udfpsOverlayInteractor + ) { mContext = context; mViewMediatorCallback = callback; mLockPatternUtils = lockPatternUtils; @@ -362,6 +367,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mAlternateBouncerInteractor = alternateBouncerInteractor; mIsBackAnimationEnabled = featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM); + mUdfpsNewTouchDetectionEnabled = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION); + mUdfpsOverlayInteractor = udfpsOverlayInteractor; } @Override @@ -1442,17 +1449,49 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } + /** + * An opportunity for the AlternateBouncer to handle the touch instead of sending + * the touch to NPVC child views. + * @return true if the alternate bouncer should consime the touch and prevent it from + * going to its child views + */ + public boolean dispatchTouchEvent(MotionEvent event) { + if (shouldInterceptTouchEvent(event) + && !mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(event)) { + onTouch(event); + } + return shouldInterceptTouchEvent(event); + } + + /** + * Whether the touch should be intercepted by the AlternateBouncer before going to the + * notification shade's child views. + */ + public boolean shouldInterceptTouchEvent(MotionEvent event) { + return mAlternateBouncerInteractor.isVisibleState(); + } + /** * For any touches on the NPVC, show the primary bouncer if the alternate bouncer is currently * showing. */ public boolean onTouch(MotionEvent event) { - boolean handledTouch = false; - if (event.getAction() == MotionEvent.ACTION_UP - && mAlternateBouncerInteractor.isVisibleState() - && mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()) { - showPrimaryBouncer(true); - handledTouch = true; + boolean handleTouch = shouldInterceptTouchEvent(event); + if (handleTouch) { + final boolean actionDown = event.getActionMasked() == MotionEvent.ACTION_DOWN; + final boolean actionDownThenUp = mAlternateBouncerInteractor.getReceivedDownTouch() + && event.getActionMasked() == MotionEvent.ACTION_UP; + final boolean udfpsOverlayWillForwardEventsOutsideNotificationShade = + mUdfpsNewTouchDetectionEnabled && mKeyguardUpdateManager.isUdfpsEnrolled(); + final boolean actionOutsideShouldDismissAlternateBouncer = + event.getActionMasked() == MotionEvent.ACTION_OUTSIDE + && !udfpsOverlayWillForwardEventsOutsideNotificationShade; + if (actionDown) { + mAlternateBouncerInteractor.setReceivedDownTouch(true); + } else if ((actionDownThenUp || actionOutsideShouldDismissAlternateBouncer) + && mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()) { + showPrimaryBouncer(true); + } } // Forward NPVC touches to callbacks in case they want to respond to touches @@ -1460,7 +1499,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb callback.onTouch(event); } - return handledTouch; + return handleTouch; } /** Update keyguard position based on a tapped X coordinate. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt index 87d5ae64dee89..9431d86f63e1f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt @@ -77,16 +77,14 @@ class UdfpsOverlayInteractorTest : SysuiTestCase() { runCurrent() - // Then touch should not be intercepted - val canInterceptTrue = underTest.canInterceptTouchInUdfpsBounds(downEv) - assertThat(canInterceptTrue).isFalse() + // Then touch is within udfps area + assertThat(underTest.isTouchWithinUdfpsArea(downEv)).isTrue() // When touch is outside of bounds whenever(overlayBounds.contains(downEv.x.toInt(), downEv.y.toInt())).thenReturn(false) - // Then touch should be intercepted - val canInterceptFalse = underTest.canInterceptTouchInUdfpsBounds(downEv) - assertThat(canInterceptFalse).isTrue() + // Then touch is not within udfps area + assertThat(underTest.isTouchWithinUdfpsArea(downEv)).isFalse() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index e65e903ef055a..629208e130afb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -26,13 +26,11 @@ import com.android.keyguard.LockIconViewController import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.R import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.dock.DockManager import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.KeyguardUnlockAnimationController -import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel @@ -69,8 +67,8 @@ import org.mockito.Mockito.anyFloat import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -97,8 +95,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController @Mock private lateinit var pulsingGestureListener: PulsingGestureListener @Mock private lateinit var notificationInsetsController: NotificationInsetsController - @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor - @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController @@ -155,8 +151,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { pulsingGestureListener, keyguardBouncerViewModel, keyguardBouncerComponentFactory, - alternateBouncerInteractor, - udfpsOverlayInteractor, keyguardTransitionInteractor, primaryBouncerToGoneTransitionViewModel, featureFlags, @@ -311,17 +305,15 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { } @Test - fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() = - testScope.runTest { - // Down event within udfpsOverlay bounds while alternateBouncer is showing - whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(DOWN_EVENT)) - .thenReturn(false) - whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true) + fun shouldInterceptTouchEvent_statusBarKeyguardViewManagerShouldIntercept() { + // down event should be intercepted by keyguardViewManager + whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT)) + .thenReturn(true) - // Then touch should not be intercepted - val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT) - assertThat(shouldIntercept).isFalse() - } + // Then touch should not be intercepted + val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT) + assertThat(shouldIntercept).isTrue() + } @Test fun testGetBouncerContainer() = diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index 2dcfdde8d74c5..b4b5ec1262346 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -26,13 +26,11 @@ import com.android.keyguard.LockIconViewController import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.R import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.dock.DockManager import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.KeyguardUnlockAnimationController -import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel @@ -103,14 +101,12 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { @Mock private lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController @Mock private lateinit var notificationInsetsController: NotificationInsetsController - @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor @Mock private lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel @Captor private lateinit var interactionEventHandlerCaptor: ArgumentCaptor - @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor private lateinit var underTest: NotificationShadeWindowView private lateinit var controller: NotificationShadeWindowViewController @@ -166,8 +162,6 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { pulsingGestureListener, keyguardBouncerViewModel, keyguardBouncerComponentFactory, - alternateBouncerInteractor, - udfpsOverlayInteractor, keyguardTransitionInteractor, primaryBouncerToGoneTransitionViewModel, featureFlags, @@ -207,8 +201,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept whenever(statusBarStateController.isDozing).thenReturn(false) - whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true) - whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(any())).thenReturn(true) + whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(any())).thenReturn(true) whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false) // THEN we should intercept touch @@ -222,7 +215,8 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept whenever(statusBarStateController.isDozing).thenReturn(false) - whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(false) + whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(any())) + .thenReturn(false) whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false) // THEN we shouldn't intercept touch @@ -236,7 +230,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept whenever(statusBarStateController.isDozing).thenReturn(false) - whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true) + whenever(statusBarKeyguardViewManager.onTouch(any())).thenReturn(true) whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false) // THEN we should handle the touch diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 31462623ce2d9..d9546877a861d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -58,6 +59,7 @@ import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.SysuiTestCase; +import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; import com.android.systemui.dock.DockManager; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.flags.FeatureFlags; @@ -126,12 +128,14 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor; @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor; @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; + @Mock private UdfpsOverlayInteractor mUdfpsOverlayInteractor; @Mock private BouncerView mBouncerView; @Mock private BouncerViewDelegate mBouncerViewDelegate; @Mock private OnBackAnimationCallback mBouncerViewDelegateBackCallback; @Mock private NotificationShadeWindowView mNotificationShadeWindowView; @Mock private WindowInsetsController mWindowInsetsController; @Mock private TaskbarDelegate mTaskbarDelegate; + @Mock private StatusBarKeyguardViewManager.KeyguardViewManagerCallback mCallback; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback @@ -188,7 +192,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mPrimaryBouncerCallbackInteractor, mPrimaryBouncerInteractor, mBouncerView, - mAlternateBouncerInteractor) { + mAlternateBouncerInteractor, + mUdfpsOverlayInteractor) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; @@ -675,7 +680,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mPrimaryBouncerCallbackInteractor, mPrimaryBouncerInteractor, mBouncerView, - mAlternateBouncerInteractor) { + mAlternateBouncerInteractor, + mUdfpsOverlayInteractor) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; @@ -713,7 +719,115 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test - public void testAlternateBouncerToShowPrimaryBouncer_updatesScrimControllerOnce() { + public void handleDispatchTouchEvent_alternateBouncerNotVisible() { + mStatusBarKeyguardViewManager.addCallback(mCallback); + + // GIVEN the alternate bouncer is visible + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); + + // THEN handleDispatchTouchEvent doesn't use the touches + assertFalse(mStatusBarKeyguardViewManager.dispatchTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + )); + assertFalse(mStatusBarKeyguardViewManager.dispatchTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) + )); + assertFalse(mStatusBarKeyguardViewManager.dispatchTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) + )); + + // THEN the touch is not acted upon + verify(mCallback, never()).onTouch(any()); + } + + @Test + public void handleDispatchTouchEvent_shouldInterceptTouchAndHandleTouch() { + mStatusBarKeyguardViewManager.addCallback(mCallback); + + // GIVEN the alternate bouncer is visible + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + + // GIVEN all touches are NOT the udfps overlay + when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false); + + // THEN handleDispatchTouchEvent eats/intercepts the touches so motion events aren't sent + // to its child views (handleDispatchTouchEvent returns true) + assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + )); + assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) + )); + assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) + )); + + // THEN the touch is acted upon once for each dispatchTOuchEvent call + verify(mCallback, times(3)).onTouch(any()); + } + + @Test + public void handleDispatchTouchEvent_shouldInterceptTouchButNotHandleTouch() { + mStatusBarKeyguardViewManager.addCallback(mCallback); + + // GIVEN the alternate bouncer is visible + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + + // GIVEN all touches are within the udfps overlay + when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(true); + + // THEN handleDispatchTouchEvent eats/intercepts the touches so motion events aren't sent + // to its child views (handleDispatchTouchEvent returns true) + assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + )); + assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) + )); + assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) + )); + + // THEN the touch is NOT acted upon at the moment + verify(mCallback, never()).onTouch(any()); + } + + @Test + public void shouldInterceptTouch_alternateBouncerNotVisible() { + // GIVEN the alternate bouncer is not visible + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); + + // THEN no motion events are intercepted + assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + )); + assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) + )); + assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) + )); + } + + @Test + public void shouldInterceptTouch_alternateBouncerVisible() { + // GIVEN the alternate bouncer is visible + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + + // THEN all motion events are intercepted + assertTrue(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + )); + assertTrue(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) + )); + assertTrue(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) + )); + } + + @Test + public void alternateBouncerToShowPrimaryBouncer_updatesScrimControllerOnce() { // GIVEN the alternate bouncer has shown and calls to hide() will result in successfully // hiding it when(mAlternateBouncerInteractor.hide()).thenReturn(true); @@ -729,30 +843,67 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test - public void testAlternateBouncerOnTouch_actionDown_doesNotHandleTouch() { + public void alternateBouncerOnTouch_actionDownThenUp_noMinTimeShown_noHideAltBouncer() { + reset(mAlternateBouncerInteractor); + // GIVEN the alternate bouncer has shown for a minimum amount of time - when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(true); + when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(false); when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false); - // WHEN ACTION_DOWN touch event comes - boolean touchHandled = mStatusBarKeyguardViewManager.onTouch( + // WHEN ACTION_DOWN and ACTION_UP touch event comes + boolean touchHandledDown = mStatusBarKeyguardViewManager.onTouch( MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); + when(mAlternateBouncerInteractor.getReceivedDownTouch()).thenReturn(true); + boolean touchHandledUp = mStatusBarKeyguardViewManager.onTouch( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)); - // THEN the touch is not handled - assertFalse(touchHandled); + // THEN the touches are handled (doesn't let touches through to underlying views) + assertTrue(touchHandledDown); + assertTrue(touchHandledUp); + + // THEN alternate bouncer does NOT attempt to hide since min showing time wasn't met + verify(mAlternateBouncerInteractor, never()).hide(); } @Test - public void testAlternateBouncerOnTouch_actionUp_handlesTouch() { + public void alternateBouncerOnTouch_actionDownThenUp_handlesTouch_hidesAltBouncer() { + reset(mAlternateBouncerInteractor); + // GIVEN the alternate bouncer has shown for a minimum amount of time when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(true); when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false); - // WHEN ACTION_UP touch event comes - boolean touchHandled = mStatusBarKeyguardViewManager.onTouch( + // WHEN ACTION_DOWN and ACTION_UP touch event comes + boolean touchHandledDown = mStatusBarKeyguardViewManager.onTouch( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); + when(mAlternateBouncerInteractor.getReceivedDownTouch()).thenReturn(true); + boolean touchHandledUp = mStatusBarKeyguardViewManager.onTouch( MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)); - // THEN the touch is handled - assertTrue(touchHandled); + // THEN the touches are handled + assertTrue(touchHandledDown); + assertTrue(touchHandledUp); + + // THEN alternate bouncer attempts to hide + verify(mAlternateBouncerInteractor).hide(); + } + + @Test + public void alternateBouncerOnTouch_actionUp_doesNotHideAlternateBouncer() { + reset(mAlternateBouncerInteractor); + + // GIVEN the alternate bouncer has shown for a minimum amount of time + when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(true); + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false); + + // WHEN only ACTION_UP touch event comes + mStatusBarKeyguardViewManager.onTouch( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)); + + // THEN the alternateBouncer doesn't hide + verify(mAlternateBouncerInteractor, never()).hide(); } }