diff options
5 files changed, 184 insertions, 76 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java index d16c8c8c59d6..0509c66f2cb6 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java @@ -21,6 +21,9 @@ import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_ import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION; import android.animation.ValueAnimator; +import android.graphics.Rect; +import android.graphics.Region; +import android.util.DisplayMetrics; import android.util.Log; import android.view.GestureDetector; import android.view.InputEvent; @@ -75,6 +78,8 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { private final FlingAnimationUtils mFlingAnimationUtils; private final FlingAnimationUtils mFlingAnimationUtilsClosing; + private final DisplayMetrics mDisplayMetrics; + private Boolean mCapture; private TouchSession mTouchSession; @@ -85,40 +90,9 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { private final GestureDetector.OnGestureListener mOnGestureListener = new GestureDetector.SimpleOnGestureListener() { - boolean mTrack; - boolean mBouncerPresent; - - @Override - public boolean onDown(MotionEvent e) { - // We only consider gestures that originate from the lower portion of the - // screen. - final float displayHeight = mStatusBar.getDisplayHeight(); - - mBouncerPresent = mStatusBar.isBouncerShowing(); - - // The target zone is either at the top or bottom of the screen, dependent on - // whether the bouncer is present. - final float zonePercentage = - Math.abs(e.getY() - (mBouncerPresent ? 0 : displayHeight)) - / displayHeight; - - mTrack = zonePercentage < mBouncerZoneScreenPercentage; - - // Never capture onDown. While this might lead to some false positive touches - // being sent to other windows/layers, this is necessary to make sure the - // proper touch event sequence is received by others in the event we do not - // consume the sequence here. - return false; - } - @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - // Do not handle scroll gestures if not tracking touch events. - if (!mTrack) { - return false; - } - if (mCapture == null) { // If the user scrolling favors a vertical direction, begin capturing // scrolls. @@ -141,8 +115,8 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { // (0). final float screenTravelPercentage = Math.abs((e1.getY() - e2.getY()) / mStatusBar.getDisplayHeight()); - setPanelExpansion( - mBouncerPresent ? screenTravelPercentage : 1 - screenTravelPercentage); + setPanelExpansion(mStatusBar.isBouncerShowing() + ? screenTravelPercentage : 1 - screenTravelPercentage); return true; } @@ -155,6 +129,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { @Inject public BouncerSwipeTouchHandler( + DisplayMetrics displayMetrics, StatusBarKeyguardViewManager statusBarKeyguardViewManager, StatusBar statusBar, NotificationShadeWindowController notificationShadeWindowController, @@ -165,6 +140,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING) FlingAnimationUtils flingAnimationUtilsClosing, @Named(SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage) { + mDisplayMetrics = displayMetrics; mStatusBar = statusBar; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mNotificationShadeWindowController = notificationShadeWindowController; @@ -176,6 +152,21 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { } @Override + public void getTouchInitiationRegion(Region region) { + if (mStatusBar.isBouncerShowing()) { + region.op(new Rect(0, 0, mDisplayMetrics.widthPixels, + Math.round(mDisplayMetrics.heightPixels * mBouncerZoneScreenPercentage)), + Region.Op.UNION); + } else { + region.op(new Rect(0, + Math.round(mDisplayMetrics.heightPixels * (1 - mBouncerZoneScreenPercentage)), + mDisplayMetrics.widthPixels, + mDisplayMetrics.heightPixels), + Region.Op.UNION); + } + } + + @Override public void onSessionStart(TouchSession session) { mVelocityTracker = mVelocityTrackerFactory.obtain(); mTouchSession = session; @@ -202,7 +193,9 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { final MotionEvent motionEvent = (MotionEvent) event; switch(motionEvent.getAction()) { + case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: + mTouchSession.pop(); // If we are not capturing any input, there is no need to consider animating to // finish transition. if (mCapture == null || !mCapture) { @@ -226,7 +219,6 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) { mStatusBarKeyguardViewManager.reset(false); } - mTouchSession.pop(); break; default: mVelocityTracker.addMovement(motionEvent); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java index 3e5efb2115a0..695b59ac45ad 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java @@ -16,6 +16,7 @@ package com.android.systemui.dreams.touch; +import android.graphics.Region; import android.view.GestureDetector; import android.view.InputEvent; import android.view.MotionEvent; @@ -34,6 +35,7 @@ import com.android.systemui.shared.system.InputChannelCompat; import com.google.common.util.concurrent.ListenableFuture; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.concurrent.Executor; @@ -100,6 +102,10 @@ public class DreamOverlayTouchMonitor { }); } + private int getSessionCount() { + return mActiveTouchSessions.size(); + } + /** * {@link TouchSessionImpl} implements {@link DreamTouchHandler.TouchSession} for * {@link DreamOverlayTouchMonitor}. It enables the monitor to access the associated listeners @@ -146,6 +152,11 @@ public class DreamOverlayTouchMonitor { return mTouchMonitor.pop(this); } + @Override + public int getActiveSessionCount() { + return mTouchMonitor.getSessionCount(); + } + /** * Returns the active listeners to receive touch events. */ @@ -229,12 +240,39 @@ public class DreamOverlayTouchMonitor { public void onInputEvent(InputEvent ev) { // No Active sessions are receiving touches. Create sessions for each listener if (mActiveTouchSessions.isEmpty()) { + final HashMap<DreamTouchHandler, DreamTouchHandler.TouchSession> sessionMap = + new HashMap<>(); + for (DreamTouchHandler handler : mHandlers) { + final Region initiationRegion = Region.obtain(); + handler.getTouchInitiationRegion(initiationRegion); + + if (!initiationRegion.isEmpty()) { + // Initiation regions require a motion event to determine pointer location + // within the region. + if (!(ev instanceof MotionEvent)) { + continue; + } + + final MotionEvent motionEvent = (MotionEvent) ev; + + // If the touch event is outside the region, then ignore. + if (!initiationRegion.contains(Math.round(motionEvent.getX()), + Math.round(motionEvent.getY()))) { + continue; + } + } + final TouchSessionImpl sessionStack = new TouchSessionImpl(DreamOverlayTouchMonitor.this, null); mActiveTouchSessions.add(sessionStack); - handler.onSessionStart(sessionStack); + sessionMap.put(handler, sessionStack); } + + // Informing handlers of new sessions is delayed until we have all created so the + // final session is correct. + sessionMap.forEach((dreamTouchHandler, touchSession) + -> dreamTouchHandler.onSessionStart(touchSession)); } // Find active sessions and invoke on InputEvent. diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java index c73ff733854d..20008d5b02c8 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java @@ -16,6 +16,7 @@ package com.android.systemui.dreams.touch; +import android.graphics.Region; import android.view.GestureDetector; import com.android.systemui.shared.system.InputChannelCompat; @@ -71,6 +72,19 @@ public interface DreamTouchHandler { * if the popped {@link TouchSession} was the initial session or has already been popped. */ ListenableFuture<TouchSession> pop(); + + /** + * Returns the number of currently active sessions. + */ + int getActiveSessionCount(); + } + + /** + * Returns the region the touch handler is interested in. By default, no region is specified, + * indicating the entire screen should be considered. + * @param region A {@link Region} that is passed in to the target entry touch region. + */ + default void getTouchInitiationRegion(Region region) { } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java index cad98f4dc713..d51151627be1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java @@ -18,7 +18,6 @@ package com.android.systemui.dreams.touch; import static com.google.common.truth.Truth.assertThat; - import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; @@ -27,12 +26,14 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.animation.ValueAnimator; +import android.graphics.Rect; +import android.graphics.Region; import android.testing.AndroidTestingRunner; +import android.util.DisplayMetrics; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.VelocityTracker; -import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -51,8 +52,6 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.util.Random; - @SmallTest @RunWith(AndroidTestingRunner.class) public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { @@ -89,13 +88,20 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { @Mock VelocityTracker mVelocityTracker; + final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); + private static final float TOUCH_REGION = .3f; - private static final float SCREEN_HEIGHT_PX = 100; + private static final int SCREEN_WIDTH_PX = 1024; + private static final int SCREEN_HEIGHT_PX = 100; @Before public void setup() { + mDisplayMetrics.widthPixels = SCREEN_WIDTH_PX; + mDisplayMetrics.heightPixels = SCREEN_HEIGHT_PX; + MockitoAnnotations.initMocks(this); mTouchHandler = new BouncerSwipeTouchHandler( + mDisplayMetrics, mStatusBarKeyguardViewManager, mStatusBar, mNotificationShadeWindowController, @@ -104,23 +110,29 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { mFlingAnimationUtils, mFlingAnimationUtilsClosing, TOUCH_REGION); - when(mStatusBar.getDisplayHeight()).thenReturn(SCREEN_HEIGHT_PX); + + when(mStatusBar.getDisplayHeight()).thenReturn((float) SCREEN_HEIGHT_PX); when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator); when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker); } - private static void beginValidSwipe(GestureDetector.OnGestureListener listener) { - listener.onDown(MotionEvent.obtain(0, 0, - MotionEvent.ACTION_DOWN, 0, - SCREEN_HEIGHT_PX - (.5f * TOUCH_REGION * SCREEN_HEIGHT_PX), 0)); - } - /** * Ensures expansion only happens when touch down happens in valid part of the screen. */ - @FlakyTest @Test public void testSessionStart() { + final Region region = Region.obtain(); + mTouchHandler.getTouchInitiationRegion(region); + + final Rect bounds = region.getBounds(); + + final Rect expected = new Rect(); + + expected.set(0, Math.round(SCREEN_HEIGHT_PX * (1 - TOUCH_REGION)), SCREEN_WIDTH_PX, + SCREEN_HEIGHT_PX); + + assertThat(bounds).isEqualTo(expected); + mTouchHandler.onSessionStart(mTouchSession); verify(mNotificationShadeWindowController).setForcePluginOpen(eq(true), any()); ArgumentCaptor<InputChannelCompat.InputEventListener> eventListenerCaptor = @@ -130,34 +142,12 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture()); verify(mTouchSession).registerInputListener(eventListenerCaptor.capture()); - final Random random = new Random(System.currentTimeMillis()); - - // If an initial touch down meeting criteria has been met, scroll behavior should be - // ignored. - assertThat(gestureListenerCaptor.getValue() - .onScroll(Mockito.mock(MotionEvent.class), - Mockito.mock(MotionEvent.class), - random.nextFloat(), - random.nextFloat())).isFalse(); - - // A touch at the top of the screen should also not trigger listening. - final MotionEvent touchDownEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, - 0, 0, 0); - - gestureListenerCaptor.getValue().onDown(touchDownEvent); - assertThat(gestureListenerCaptor.getValue() - .onScroll(Mockito.mock(MotionEvent.class), - Mockito.mock(MotionEvent.class), - random.nextFloat(), - random.nextFloat())).isFalse(); - // A touch within range at the bottom of the screen should trigger listening - beginValidSwipe(gestureListenerCaptor.getValue()); assertThat(gestureListenerCaptor.getValue() .onScroll(Mockito.mock(MotionEvent.class), Mockito.mock(MotionEvent.class), - random.nextFloat(), - random.nextFloat())).isTrue(); + 1, + 2)).isTrue(); } /** @@ -170,8 +160,6 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class); verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture()); - beginValidSwipe(gestureListenerCaptor.getValue()); - final float scrollAmount = .3f; final float distanceY = SCREEN_HEIGHT_PX * scrollAmount; @@ -203,8 +191,6 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { when(mVelocityTracker.getYVelocity()).thenReturn(velocityY); - beginValidSwipe(gestureListenerCaptor.getValue()); - final float distanceY = SCREEN_HEIGHT_PX * position; final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java index 74b217b2fdbf..29f56e0d8bf0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java @@ -21,10 +21,13 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.graphics.Rect; +import android.graphics.Region; import android.testing.AndroidTestingRunner; import android.util.Pair; import android.view.GestureDetector; @@ -137,6 +140,81 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase { } @Test + public void testEntryTouchZone() { + final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class); + final Rect touchArea = new Rect(4, 4, 8 , 8); + + doAnswer(invocation -> { + final Region region = (Region) invocation.getArguments()[0]; + region.set(touchArea); + return null; + }).when(touchHandler).getTouchInitiationRegion(any()); + + final Environment environment = new Environment(Stream.of(touchHandler) + .collect(Collectors.toCollection(HashSet::new))); + + // Ensure touch outside specified region is not delivered. + final MotionEvent initialEvent = Mockito.mock(MotionEvent.class); + when(initialEvent.getX()).thenReturn(0.0f); + when(initialEvent.getY()).thenReturn(1.0f); + environment.publishInputEvent(initialEvent); + verify(touchHandler, never()).onSessionStart(any()); + + // Make sure touch inside region causes session start. + when(initialEvent.getX()).thenReturn(5.0f); + when(initialEvent.getY()).thenReturn(5.0f); + environment.publishInputEvent(initialEvent); + verify(touchHandler).onSessionStart(any()); + } + + @Test + public void testSessionCount() { + final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class); + final Rect touchArea = new Rect(4, 4, 8 , 8); + + final DreamTouchHandler unzonedTouchHandler = Mockito.mock(DreamTouchHandler.class); + doAnswer(invocation -> { + final Region region = (Region) invocation.getArguments()[0]; + region.set(touchArea); + return null; + }).when(touchHandler).getTouchInitiationRegion(any()); + + final Environment environment = new Environment(Stream.of(touchHandler, unzonedTouchHandler) + .collect(Collectors.toCollection(HashSet::new))); + + // Ensure touch outside specified region is delivered to unzoned touch handler. + final MotionEvent initialEvent = Mockito.mock(MotionEvent.class); + when(initialEvent.getX()).thenReturn(0.0f); + when(initialEvent.getY()).thenReturn(1.0f); + environment.publishInputEvent(initialEvent); + + ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionCaptor = ArgumentCaptor.forClass( + DreamTouchHandler.TouchSession.class); + + // Make sure only one active session. + { + verify(unzonedTouchHandler).onSessionStart(touchSessionCaptor.capture()); + final DreamTouchHandler.TouchSession touchSession = touchSessionCaptor.getValue(); + assertThat(touchSession.getActiveSessionCount()).isEqualTo(1); + touchSession.pop(); + environment.executeAll(); + } + + // Make sure touch inside the touch region. + when(initialEvent.getX()).thenReturn(5.0f); + when(initialEvent.getY()).thenReturn(5.0f); + environment.publishInputEvent(initialEvent); + + // Make sure there are two active sessions. + { + verify(touchHandler).onSessionStart(touchSessionCaptor.capture()); + final DreamTouchHandler.TouchSession touchSession = touchSessionCaptor.getValue(); + assertThat(touchSession.getActiveSessionCount()).isEqualTo(2); + touchSession.pop(); + } + } + + @Test public void testInputEventPropagation() { final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class); |