From 0ad8657ad7556e606e41e4fbd632819543de392a Mon Sep 17 00:00:00 2001 From: Beverly Date: Wed, 24 Aug 2022 19:41:20 +0000 Subject: [PATCH] Add falsing protection to the pulsing state Prox-gate the tap and double tap to wake gestures from pulsing/AoD2. Enable FalsingCollector logs: adb shell setprop log.tag.FalsingCollector DEBUG Test: manually trigger quickpickup (or receive a notification on AoD), cover the prox sensor, and then tap the screen - see device won't wake up Test: atest NotificationShadeWindowViewControllerTest BrightLineFalsingManagerTest PulsingGestureListenerTest Fixes: 242125976 Change-Id: I520df509b7804d765807477968257c94f55ef634 --- .../systemui/plugins/FalsingManager.java | 6 + .../classifier/BrightLineFalsingManager.java | 8 + .../classifier/FalsingCollectorImpl.java | 6 +- .../classifier/FalsingManagerProxy.java | 5 + ...NotificationShadeWindowViewController.java | 56 +---- .../systemui/shade/PulsingGestureListener.kt | 111 ++++++++++ .../BrightLineFalsingManagerTest.java | 47 +++++ ...tificationShadeWindowViewControllerTest.kt | 11 +- .../NotificationShadeWindowViewTest.java | 6 +- .../shade/PulsingGestureListenerTest.kt | 199 ++++++++++++++++++ .../classifier/FalsingManagerFake.java | 10 + 11 files changed, 404 insertions(+), 61 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java index bd628ccb3c08d..1aaf19e8793f0 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java @@ -102,6 +102,12 @@ public interface FalsingManager { */ boolean isFalseDoubleTap(); + /** + * Whether the last proximity event reported NEAR. May be used to short circuit motion events + * that require the proximity sensor is not covered. + */ + boolean isProximityNear(); + boolean isClassifierEnabled(); boolean shouldEnforceBouncer(); diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java index e28a4755bd00f..31a2134851a2c 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java @@ -80,12 +80,14 @@ public class BrightLineFalsingManager implements FalsingManager { private final Collection mClassifiers; private final List mFalsingBeliefListeners = new ArrayList<>(); private List mFalsingTapListeners = new ArrayList<>(); + private ProximityEvent mLastProximityEvent; private boolean mDestroyed; private final SessionListener mSessionListener = new SessionListener() { @Override public void onSessionEnded() { + mLastProximityEvent = null; mClassifiers.forEach(FalsingClassifier::onSessionEnded); } @@ -336,6 +338,7 @@ public class BrightLineFalsingManager implements FalsingManager { public void onProximityEvent(ProximityEvent proximityEvent) { // TODO: some of these classifiers might allow us to abort early, meaning we don't have to // make these calls. + mLastProximityEvent = proximityEvent; mClassifiers.forEach((classifier) -> classifier.onProximityEvent(proximityEvent)); } @@ -347,6 +350,11 @@ public class BrightLineFalsingManager implements FalsingManager { } } + @Override + public boolean isProximityNear() { + return mLastProximityEvent != null && mLastProximityEvent.getCovered(); + } + @Override public boolean isUnlockingDisabled() { return false; diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java index 56dd1e18f2454..23d87ff980ca6 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java @@ -47,9 +47,9 @@ import javax.inject.Inject; @SysUISingleton class FalsingCollectorImpl implements FalsingCollector { - private static final boolean DEBUG = false; - private static final String TAG = "FalsingManager"; - private static final String PROXIMITY_SENSOR_TAG = "FalsingManager"; + private static final String TAG = "FalsingCollector"; + private static final String PROXIMITY_SENSOR_TAG = "FalsingCollector"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final long GESTURE_PROCESSING_DELAY_MS = 100; private final FalsingDataProvider mFalsingDataProvider; diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java index 609f1d6d9b896..5d04b5f774793 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java @@ -143,6 +143,11 @@ public class FalsingManagerProxy implements FalsingManager, Dumpable { return mInternalFalsingManager.isFalseDoubleTap(); } + @Override + public boolean isProximityNear() { + return mInternalFalsingManager.isProximityNear(); + } + @Override public boolean isClassifierEnabled() { return mInternalFalsingManager.isClassifierEnabled(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index b8546df75f5f4..fec76b65dd093 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -17,12 +17,9 @@ package com.android.systemui.shade; import android.app.StatusBarManager; -import android.hardware.display.AmbientDisplayConfiguration; import android.media.AudioManager; import android.media.session.MediaSessionLegacyHelper; import android.os.SystemClock; -import android.os.UserHandle; -import android.provider.Settings; import android.util.Log; import android.view.GestureDetector; import android.view.InputDevice; @@ -52,7 +49,6 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.window.StatusBarWindowStateController; -import com.android.systemui.tuner.TunerService; import java.io.PrintWriter; import java.util.Optional; @@ -66,7 +62,6 @@ import javax.inject.Inject; public class NotificationShadeWindowViewController { private static final String TAG = "NotifShadeWindowVC"; private final FalsingCollector mFalsingCollector; - private final TunerService mTunerService; private final SysuiStatusBarStateController mStatusBarStateController; private final NotificationShadeWindowView mView; private final NotificationShadeDepthController mDepthController; @@ -77,6 +72,7 @@ public class NotificationShadeWindowViewController { private final StatusBarWindowStateController mStatusBarWindowStateController; private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private final AmbientState mAmbientState; + private final PulsingGestureListener mPulsingGestureListener; private GestureDetector mGestureDetector; private View mBrightnessMirror; @@ -88,8 +84,6 @@ public class NotificationShadeWindowViewController { private final CentralSurfaces mService; private final NotificationShadeWindowController mNotificationShadeWindowController; private DragDownHelper mDragDownHelper; - private boolean mDoubleTapEnabled; - private boolean mSingleTapEnabled; private boolean mExpandingBelowNotch; private final DockManager mDockManager; private final NotificationPanelViewController mNotificationPanelViewController; @@ -102,7 +96,6 @@ public class NotificationShadeWindowViewController { public NotificationShadeWindowViewController( LockscreenShadeTransitionController transitionController, FalsingCollector falsingCollector, - TunerService tunerService, SysuiStatusBarStateController statusBarStateController, DockManager dockManager, NotificationShadeDepthController depthController, @@ -117,10 +110,11 @@ public class NotificationShadeWindowViewController { CentralSurfaces centralSurfaces, NotificationShadeWindowController controller, KeyguardUnlockAnimationController keyguardUnlockAnimationController, - AmbientState ambientState) { + AmbientState ambientState, + PulsingGestureListener pulsingGestureListener + ) { mLockscreenShadeTransitionController = transitionController; mFalsingCollector = falsingCollector; - mTunerService = tunerService; mStatusBarStateController = statusBarStateController; mView = notificationShadeWindowView; mDockManager = dockManager; @@ -136,6 +130,7 @@ public class NotificationShadeWindowViewController { mNotificationShadeWindowController = controller; mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; mAmbientState = ambientState; + mPulsingGestureListener = pulsingGestureListener; // This view is not part of the newly inflated expanded status bar. mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container); @@ -151,46 +146,7 @@ public class NotificationShadeWindowViewController { /** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */ public void setupExpandedStatusBar() { mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller); - - TunerService.Tunable tunable = (key, newValue) -> { - AmbientDisplayConfiguration configuration = - new AmbientDisplayConfiguration(mView.getContext()); - switch (key) { - case Settings.Secure.DOZE_DOUBLE_TAP_GESTURE: - mDoubleTapEnabled = configuration.doubleTapGestureEnabled( - UserHandle.USER_CURRENT); - break; - case Settings.Secure.DOZE_TAP_SCREEN_GESTURE: - mSingleTapEnabled = configuration.tapGestureEnabled(UserHandle.USER_CURRENT); - } - }; - mTunerService.addTunable(tunable, - Settings.Secure.DOZE_DOUBLE_TAP_GESTURE, - Settings.Secure.DOZE_TAP_SCREEN_GESTURE); - - GestureDetector.SimpleOnGestureListener gestureListener = - new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onSingleTapConfirmed(MotionEvent e) { - if (mSingleTapEnabled && !mDockManager.isDocked()) { - mService.wakeUpIfDozing( - SystemClock.uptimeMillis(), mView, "SINGLE_TAP"); - return true; - } - return false; - } - - @Override - public boolean onDoubleTap(MotionEvent e) { - if (mDoubleTapEnabled || mSingleTapEnabled) { - mService.wakeUpIfDozing( - SystemClock.uptimeMillis(), mView, "DOUBLE_TAP"); - return true; - } - return false; - } - }; - mGestureDetector = new GestureDetector(mView.getContext(), gestureListener); + mGestureDetector = new GestureDetector(mView.getContext(), mPulsingGestureListener); mLowLightClockController.ifPresent(controller -> controller.attachLowLightClockView(mView)); diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt new file mode 100644 index 0000000000000..621a6090f4a00 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade + +import android.hardware.display.AmbientDisplayConfiguration +import android.os.SystemClock +import android.os.UserHandle +import android.provider.Settings +import android.view.GestureDetector +import android.view.MotionEvent +import com.android.systemui.Dumpable +import com.android.systemui.dock.DockManager +import com.android.systemui.dump.DumpManager +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.statusbar.phone.CentralSurfaces +import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent +import com.android.systemui.tuner.TunerService +import com.android.systemui.tuner.TunerService.Tunable +import java.io.PrintWriter +import javax.inject.Inject + +/** + * If tap and/or double tap to wake is enabled, this gestureListener will wake the display on + * tap/double tap when the device is pulsing (AoD 2). Taps are gated by the proximity sensor and + * falsing manager. + * + * Touches go through the [NotificationShadeWindowViewController] when the device is pulsing. + * Otherwise, if the device is dozing and NOT pulsing, wake-ups are handled by + * [com.android.systemui.doze.DozeSensors]. + */ +@CentralSurfacesComponent.CentralSurfacesScope +class PulsingGestureListener @Inject constructor( + private val notificationShadeWindowView: NotificationShadeWindowView, + private val falsingManager: FalsingManager, + private val dockManager: DockManager, + private val centralSurfaces: CentralSurfaces, + private val ambientDisplayConfiguration: AmbientDisplayConfiguration, + tunerService: TunerService, + dumpManager: DumpManager +) : GestureDetector.SimpleOnGestureListener(), Dumpable { + private var doubleTapEnabled = false + private var singleTapEnabled = false + + init { + val tunable = Tunable { key: String?, _: String? -> + when (key) { + Settings.Secure.DOZE_DOUBLE_TAP_GESTURE -> + doubleTapEnabled = ambientDisplayConfiguration.doubleTapGestureEnabled( + UserHandle.USER_CURRENT) + Settings.Secure.DOZE_TAP_SCREEN_GESTURE -> + singleTapEnabled = ambientDisplayConfiguration.tapGestureEnabled( + UserHandle.USER_CURRENT) + } + } + tunerService.addTunable(tunable, + Settings.Secure.DOZE_DOUBLE_TAP_GESTURE, + Settings.Secure.DOZE_TAP_SCREEN_GESTURE) + + dumpManager.registerDumpable(this) + } + + override fun onSingleTapConfirmed(e: MotionEvent): Boolean { + if (singleTapEnabled && + !dockManager.isDocked && + !falsingManager.isProximityNear && + !falsingManager.isFalseTap(FalsingManager.MODERATE_PENALTY) + ) { + centralSurfaces.wakeUpIfDozing( + SystemClock.uptimeMillis(), + notificationShadeWindowView, + "PULSING_SINGLE_TAP") + return true + } + return false + } + + override fun onDoubleTap(e: MotionEvent): Boolean { + if ((doubleTapEnabled || singleTapEnabled) && + !falsingManager.isProximityNear && + !falsingManager.isFalseDoubleTap + ) { + centralSurfaces.wakeUpIfDozing( + SystemClock.uptimeMillis(), + notificationShadeWindowView, + "PULSING_DOUBLE_TAP") + return true + } + return false + } + + override fun dump(pw: PrintWriter, args: Array) { + pw.println("singleTapEnabled=$singleTapEnabled") + pw.println("doubleTapEnabled=$doubleTapEnabled") + pw.println("isDocked=${dockManager.isDocked}") + pw.println("isProxCovered=${falsingManager.isProximityNear}") + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java index 55ee433c8d9de..94813497cb4c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java @@ -32,6 +32,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.testing.FakeMetricsLogger; import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Before; @@ -111,5 +112,51 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { assertThat(mBrightLineFalsingManager.isFalseDoubleTap()).isFalse(); } + @Test + public void testIsProxNear_noProxEvents_defaultsToFalse() { + assertThat(mBrightLineFalsingManager.isProximityNear()).isFalse(); + } + @Test + public void testIsProxNear_receivesNearEvent() { + mBrightLineFalsingManager.onProximityEvent(new FalsingManager.ProximityEvent() { + @Override + public boolean getCovered() { + return true; + } + + @Override + public long getTimestampNs() { + return 0; + } + }); + assertThat(mBrightLineFalsingManager.isProximityNear()).isTrue(); + } + + @Test + public void testIsProxNear_receivesNearAndThenFarEvent() { + mBrightLineFalsingManager.onProximityEvent(new FalsingManager.ProximityEvent() { + @Override + public boolean getCovered() { + return true; + } + + @Override + public long getTimestampNs() { + return 0; + } + }); + mBrightLineFalsingManager.onProximityEvent(new FalsingManager.ProximityEvent() { + @Override + public boolean getCovered() { + return false; + } + + @Override + public long getTimestampNs() { + return 5; + } + }); + assertThat(mBrightLineFalsingManager.isProximityNear()).isFalse(); + } } 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 471918cfa695b..6d059b1cb7873 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -26,19 +26,18 @@ import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.dock.DockManager import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.lowlightclock.LowLightClockController +import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.NotificationShadeDepthController import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController -import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.PhoneStatusBarViewController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager import com.android.systemui.statusbar.window.StatusBarWindowStateController -import com.android.systemui.tuner.TunerService import com.google.common.truth.Truth.assertThat import java.util.Optional import org.junit.Before @@ -60,8 +59,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock private lateinit var view: NotificationShadeWindowView @Mock - private lateinit var tunserService: TunerService - @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController @Mock private lateinit var centralSurfaces: CentralSurfaces @@ -91,6 +88,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController @Mock private lateinit var lowLightClockController: LowLightClockController + @Mock + private lateinit var pulsingGestureListener: PulsingGestureListener private lateinit var interactionEventHandlerCaptor: ArgumentCaptor private lateinit var interactionEventHandler: InteractionEventHandler @@ -105,7 +104,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { underTest = NotificationShadeWindowViewController( lockscreenShadeTransitionController, FalsingCollectorFake(), - tunserService, sysuiStatusBarStateController, dockManager, notificationShadeDepthController, @@ -120,7 +118,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { centralSurfaces, notificationShadeWindowController, keyguardUnlockAnimationController, - ambientState + ambientState, + pulsingGestureListener ) underTest.setupExpandedStatusBar() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java index fa16fefdf5a40..89a251808402c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java @@ -89,6 +89,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { @Mock private LowLightClockController mLowLightClockController; @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; @Mock private AmbientState mAmbientState; + @Mock private PulsingGestureListener mPulsingGestureListener; @Captor private ArgumentCaptor mInteractionEventHandlerCaptor; @@ -110,7 +111,6 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { mController = new NotificationShadeWindowViewController( mLockscreenShadeTransitionController, new FalsingCollectorFake(), - mTunerService, mStatusBarStateController, mDockManager, mNotificationShadeDepthController, @@ -125,7 +125,9 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { mCentralSurfaces, mNotificationShadeWindowController, mKeyguardUnlockAnimationController, - mAmbientState); + mAmbientState, + mPulsingGestureListener + ); mController.setupExpandedStatusBar(); mController.setDragDownHelper(mDragDownHelper); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt new file mode 100644 index 0000000000000..d2970a63a8601 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade + +import android.hardware.display.AmbientDisplayConfiguration +import android.provider.Settings.Secure.DOZE_DOUBLE_TAP_GESTURE +import android.provider.Settings.Secure.DOZE_TAP_SCREEN_GESTURE +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.MotionEvent +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dock.DockManager +import com.android.systemui.dump.DumpManager +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.statusbar.phone.CentralSurfaces +import com.android.systemui.tuner.TunerService +import com.android.systemui.tuner.TunerService.Tunable +import com.android.systemui.util.mockito.eq +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyLong +import org.mockito.ArgumentMatchers.anyObject +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@RunWithLooper(setAsMainLooper = true) +@SmallTest +class PulsingGestureListenerTest : SysuiTestCase() { + @Mock + private lateinit var view: NotificationShadeWindowView + @Mock + private lateinit var centralSurfaces: CentralSurfaces + @Mock + private lateinit var dockManager: DockManager + @Mock + private lateinit var falsingManager: FalsingManager + @Mock + private lateinit var ambientDisplayConfiguration: AmbientDisplayConfiguration + @Mock + private lateinit var tunerService: TunerService + @Mock + private lateinit var dumpManager: DumpManager + + private lateinit var tunableCaptor: ArgumentCaptor + private lateinit var underTest: PulsingGestureListener + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + underTest = PulsingGestureListener( + view, + falsingManager, + dockManager, + centralSurfaces, + ambientDisplayConfiguration, + tunerService, + dumpManager + ) + whenever(dockManager.isDocked).thenReturn(false) + } + + @Test + fun testGestureDetector_singleTapEnabled() { + // GIVEN tap is enabled, prox not covered + whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) + updateSettings() + whenever(falsingManager.isProximityNear).thenReturn(false) + + // GIVEN the falsing manager does NOT think the tap is a false tap + whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false) + + // WHEN there's a tap + underTest.onSingleTapConfirmed(downEv) + + // THEN wake up device if dozing + verify(centralSurfaces).wakeUpIfDozing(anyLong(), anyObject(), anyString()) + } + + @Test + fun testGestureDetector_doubleTapEnabled() { + // GIVEN double tap is enabled, prox not covered + whenever(ambientDisplayConfiguration.doubleTapGestureEnabled(anyInt())).thenReturn(true) + updateSettings() + whenever(falsingManager.isProximityNear).thenReturn(false) + + // GIVEN the falsing manager does NOT think the tap is a false tap + whenever(falsingManager.isFalseDoubleTap).thenReturn(false) + + // WHEN there's a double tap + underTest.onDoubleTap(downEv) + + // THEN wake up device if dozing + verify(centralSurfaces).wakeUpIfDozing(anyLong(), anyObject(), anyString()) + } + + @Test + fun testGestureDetector_singleTapEnabled_falsing() { + // GIVEN tap is enabled, prox not covered + whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) + updateSettings() + whenever(falsingManager.isProximityNear).thenReturn(false) + + // GIVEN the falsing manager thinks the tap is a false tap + whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true) + + // WHEN there's a tap + underTest.onSingleTapConfirmed(downEv) + + // THEN the device doesn't wake up + verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString()) + } + + @Test + fun testGestureDetector_doubleTapEnabled_falsing() { + // GIVEN double tap is enabled, prox not covered + whenever(ambientDisplayConfiguration.doubleTapGestureEnabled(anyInt())).thenReturn(true) + updateSettings() + whenever(falsingManager.isProximityNear).thenReturn(false) + + // GIVEN the falsing manager thinks the tap is a false tap + whenever(falsingManager.isFalseDoubleTap).thenReturn(true) + + // WHEN there's a tap + underTest.onDoubleTap(downEv) + + // THEN the device doesn't wake up + verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString()) + } + + @Test + fun testGestureDetector_singleTapEnabled_proxCovered() { + // GIVEN tap is enabled, not a false tap based on classifiers + whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) + updateSettings() + whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false) + + // GIVEN prox is covered + whenever(falsingManager.isProximityNear()).thenReturn(true) + + // WHEN there's a tap + underTest.onSingleTapConfirmed(downEv) + + // THEN the device doesn't wake up + verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString()) + } + + @Test + fun testGestureDetector_doubleTapEnabled_proxCovered() { + // GIVEN double tap is enabled, not a false tap based on classifiers + whenever(ambientDisplayConfiguration.doubleTapGestureEnabled(anyInt())).thenReturn(true) + updateSettings() + whenever(falsingManager.isFalseDoubleTap).thenReturn(false) + + // GIVEN prox is covered + whenever(falsingManager.isProximityNear()).thenReturn(true) + + // WHEN there's a tap + underTest.onDoubleTap(downEv) + + // THEN the device doesn't wake up + verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString()) + } + + fun updateSettings() { + tunableCaptor = ArgumentCaptor.forClass(Tunable::class.java) + verify(tunerService).addTunable( + tunableCaptor.capture(), + eq(DOZE_DOUBLE_TAP_GESTURE), + eq(DOZE_TAP_SCREEN_GESTURE)) + tunableCaptor.value.onTuningChanged(DOZE_DOUBLE_TAP_GESTURE, "") + tunableCaptor.value.onTuningChanged(DOZE_TAP_SCREEN_GESTURE, "") + } +} + +private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java index 48b5c62da38f5..34c83bd41a022 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java @@ -40,6 +40,7 @@ public class FalsingManagerFake implements FalsingManager { private boolean mIsReportingEnabled; private boolean mIsFalseRobustTap; private boolean mDestroyed; + private boolean mIsProximityNear; private final List mFalsingBeliefListeners = new ArrayList<>(); private final List mTapListeners = new ArrayList<>(); @@ -82,6 +83,10 @@ public class FalsingManagerFake implements FalsingManager { mIsFalseDoubleTap = falseDoubleTap; } + public void setIsProximityNear(boolean proxNear) { + mIsProximityNear = proxNear; + } + @Override public boolean isSimpleTap() { checkDestroyed(); @@ -100,6 +105,11 @@ public class FalsingManagerFake implements FalsingManager { return mIsFalseDoubleTap; } + @Override + public boolean isProximityNear() { + return mIsProximityNear; + } + @VisibleForTesting public void setIsClassifierEnabled(boolean isClassifierEnabled) { mIsClassifierEnabled = isClassifierEnabled;