Merge "Add Touch Initiation Regions." into tm-dev
This commit is contained in:
@@ -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;
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user