Loading packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java +26 −34 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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. Loading @@ -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; } Loading @@ -155,6 +129,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { @Inject public BouncerSwipeTouchHandler( DisplayMetrics displayMetrics, StatusBarKeyguardViewManager statusBarKeyguardViewManager, StatusBar statusBar, NotificationShadeWindowController notificationShadeWindowController, Loading @@ -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; Loading @@ -175,6 +151,21 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { mVelocityTrackerFactory = velocityTrackerFactory; } @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(); Loading Loading @@ -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) { Loading @@ -226,7 +219,6 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) { mStatusBarKeyguardViewManager.reset(false); } mTouchSession.pop(); break; default: mVelocityTracker.addMovement(motionEvent); Loading packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java +39 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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. */ Loading Loading @@ -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. Loading packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java +14 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { } /** Loading packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java +27 −41 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 { Loading Loading @@ -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, Loading @@ -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 = Loading @@ -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(); } /** Loading @@ -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; Loading Loading @@ -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, Loading packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java +78 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -136,6 +139,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); Loading Loading
packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java +26 −34 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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. Loading @@ -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; } Loading @@ -155,6 +129,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { @Inject public BouncerSwipeTouchHandler( DisplayMetrics displayMetrics, StatusBarKeyguardViewManager statusBarKeyguardViewManager, StatusBar statusBar, NotificationShadeWindowController notificationShadeWindowController, Loading @@ -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; Loading @@ -175,6 +151,21 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { mVelocityTrackerFactory = velocityTrackerFactory; } @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(); Loading Loading @@ -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) { Loading @@ -226,7 +219,6 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) { mStatusBarKeyguardViewManager.reset(false); } mTouchSession.pop(); break; default: mVelocityTracker.addMovement(motionEvent); Loading
packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java +39 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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. */ Loading Loading @@ -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. Loading
packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java +14 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { } /** Loading
packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java +27 −41 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 { Loading Loading @@ -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, Loading @@ -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 = Loading @@ -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(); } /** Loading @@ -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; Loading Loading @@ -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, Loading
packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java +78 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -136,6 +139,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); Loading