Merge "Fix touchexploration multi-finger gesture conflict" into sc-dev
This commit is contained in:
@@ -98,8 +98,7 @@ public final class GesturesObserver implements GestureMatcher.StateChangeListene
|
||||
}
|
||||
mProcessMotionEvent = true;
|
||||
for (int i = 0; i < mGestureMatchers.size(); i++) {
|
||||
final GestureMatcher matcher =
|
||||
mGestureMatchers.get(i);
|
||||
final GestureMatcher matcher = mGestureMatchers.get(i);
|
||||
matcher.onMotionEvent(event, rawEvent, policyFlags);
|
||||
if (matcher.getState() == GestureMatcher.STATE_GESTURE_COMPLETED) {
|
||||
clear();
|
||||
@@ -128,7 +127,10 @@ public final class GesturesObserver implements GestureMatcher.StateChangeListene
|
||||
MotionEvent rawEvent, int policyFlags) {
|
||||
if (state == GestureMatcher.STATE_GESTURE_COMPLETED) {
|
||||
mListener.onGestureCompleted(gestureId, event, rawEvent, policyFlags);
|
||||
//Clear the states in onMotionEvent().
|
||||
// Ideally we clear the states in onMotionEvent(), this case is for hold gestures.
|
||||
// If we clear before processing up event , then MultiTap matcher cancels the gesture
|
||||
// due to incorrect state. It ends up listener#onGestureCancelled is called even
|
||||
// the gesture is detected.
|
||||
if (!mProcessMotionEvent) {
|
||||
clear();
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
class MagnificationGestureMatcher {
|
||||
|
||||
private static final int GESTURE_BASE = 100;
|
||||
public static final int GESTURE_TWO_FINGER_DOWN = GESTURE_BASE + 1;
|
||||
public static final int GESTURE_TWO_FINGERS_DOWN_OR_SWIPE = GESTURE_BASE + 1;
|
||||
public static final int GESTURE_SWIPE = GESTURE_BASE + 2;
|
||||
public static final int GESTURE_SINGLE_TAP = GESTURE_BASE + 3;
|
||||
public static final int GESTURE_SINGLE_TAP_AND_HOLD = GESTURE_BASE + 4;
|
||||
@@ -41,7 +41,7 @@ class MagnificationGestureMatcher {
|
||||
public static final int GESTURE_TRIPLE_TAP_AND_HOLD = GESTURE_BASE + 6;
|
||||
|
||||
@IntDef(prefix = {"GESTURE_MAGNIFICATION_"}, value = {
|
||||
GESTURE_TWO_FINGER_DOWN,
|
||||
GESTURE_TWO_FINGERS_DOWN_OR_SWIPE,
|
||||
GESTURE_SWIPE
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@@ -57,8 +57,8 @@ class MagnificationGestureMatcher {
|
||||
switch (gestureId) {
|
||||
case GESTURE_SWIPE:
|
||||
return "GESTURE_SWIPE";
|
||||
case GESTURE_TWO_FINGER_DOWN:
|
||||
return "GESTURE_TWO_FINGER_DOWN";
|
||||
case GESTURE_TWO_FINGERS_DOWN_OR_SWIPE:
|
||||
return "GESTURE_TWO_FINGERS_DOWN_OR_SWIPE";
|
||||
case GESTURE_SINGLE_TAP:
|
||||
return "GESTURE_SINGLE_TAP";
|
||||
case GESTURE_SINGLE_TAP_AND_HOLD:
|
||||
@@ -71,6 +71,12 @@ class MagnificationGestureMatcher {
|
||||
return "none";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context
|
||||
* @return the duration in milliseconds between the first tap's down event and
|
||||
* the second tap's down event to be considered that the user is going to performing
|
||||
* panning/scaling gesture.
|
||||
*/
|
||||
static int getMagnificationMultiTapTimeout(Context context) {
|
||||
return ViewConfiguration.getDoubleTapTimeout() + context.getResources().getInteger(
|
||||
R.integer.config_screen_magnification_multi_tap_adjustment);
|
||||
|
||||
@@ -65,7 +65,7 @@ class MagnificationGesturesObserver implements GesturesObserver.Listener {
|
||||
* the last event before timeout.
|
||||
*
|
||||
* @see MagnificationGestureMatcher#GESTURE_SWIPE
|
||||
* @see MagnificationGestureMatcher#GESTURE_TWO_FINGER_DOWN
|
||||
* @see MagnificationGestureMatcher#GESTURE_TWO_FINGERS_DOWN_OR_SWIPE
|
||||
*/
|
||||
void onGestureCompleted(@GestureId int gestureId, long lastDownEventTime,
|
||||
List<MotionEventInfo> delayedEventQueue, MotionEvent event);
|
||||
|
||||
@@ -48,6 +48,11 @@ class SimpleSwipe extends GestureMatcher {
|
||||
cancelAfter(mDetectionDurationMillis, event, rawEvent, policyFlags);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
|
||||
cancelGesture(event, rawEvent, policyFlags);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
|
||||
if (gestureMatched(event, rawEvent, policyFlags)) {
|
||||
@@ -65,7 +70,7 @@ class SimpleSwipe extends GestureMatcher {
|
||||
}
|
||||
|
||||
private boolean gestureMatched(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
|
||||
return mLastDown != null && (distance(mLastDown, event) >= mSwipeMinDistance);
|
||||
return mLastDown != null && (distance(mLastDown, event) > mSwipeMinDistance);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.server.accessibility.magnification;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.android.server.accessibility.gestures.GestureMatcher;
|
||||
|
||||
/**
|
||||
*
|
||||
* This class is responsible for matching two fingers down gestures. The gesture matching
|
||||
* result is determined in a duration.
|
||||
*/
|
||||
final class TwoFingersDown extends GestureMatcher {
|
||||
|
||||
private MotionEvent mLastDown;
|
||||
private final int mDetectionDurationMillis;
|
||||
|
||||
TwoFingersDown(Context context) {
|
||||
super(MagnificationGestureMatcher.GESTURE_TWO_FINGER_DOWN,
|
||||
new Handler(context.getMainLooper()), null);
|
||||
mDetectionDurationMillis = MagnificationGestureMatcher.getMagnificationMultiTapTimeout(
|
||||
context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
|
||||
mLastDown = MotionEvent.obtain(event);
|
||||
cancelAfter(mDetectionDurationMillis, event, rawEvent, policyFlags);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
|
||||
if (mLastDown == null) {
|
||||
cancelGesture(event, rawEvent, policyFlags);
|
||||
}
|
||||
completeGesture(event, rawEvent, policyFlags);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
|
||||
cancelGesture(event, rawEvent, policyFlags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
if (mLastDown != null) {
|
||||
mLastDown.recycle();
|
||||
mLastDown = null;
|
||||
}
|
||||
super.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getGestureName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.server.accessibility.magnification;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.MathUtils;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import com.android.server.accessibility.gestures.GestureMatcher;
|
||||
|
||||
/**
|
||||
* This class is responsible for detecting that the user is using two fingers to perform
|
||||
* swiping gestures or just stay pressed on the screen. The gesture matching result is determined
|
||||
* in a duration.
|
||||
*/
|
||||
final class TwoFingersDownOrSwipe extends GestureMatcher {
|
||||
|
||||
private final int mDoubleTapTimeout;
|
||||
private final int mDetectionDurationMillis;
|
||||
private final int mSwipeMinDistance;
|
||||
private MotionEvent mFirstPointerDown;
|
||||
private MotionEvent mSecondPointerDown;
|
||||
|
||||
TwoFingersDownOrSwipe(Context context) {
|
||||
super(MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE,
|
||||
new Handler(context.getMainLooper()), null);
|
||||
mDetectionDurationMillis = MagnificationGestureMatcher.getMagnificationMultiTapTimeout(
|
||||
context);
|
||||
mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
|
||||
mSwipeMinDistance = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
|
||||
mFirstPointerDown = MotionEvent.obtain(event);
|
||||
cancelAfter(mDetectionDurationMillis, event, rawEvent, policyFlags);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
|
||||
if (mFirstPointerDown == null) {
|
||||
cancelGesture(event, rawEvent, policyFlags);
|
||||
}
|
||||
if (event.getPointerCount() == 2) {
|
||||
mSecondPointerDown = MotionEvent.obtain(event);
|
||||
completeAfter(mDoubleTapTimeout, event, rawEvent, policyFlags);
|
||||
} else {
|
||||
cancelGesture(event, rawEvent, policyFlags);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
|
||||
if (mFirstPointerDown == null || mSecondPointerDown == null) {
|
||||
return;
|
||||
}
|
||||
if (distance(mFirstPointerDown, /* move */ event) > mSwipeMinDistance) {
|
||||
completeGesture(event, rawEvent, policyFlags);
|
||||
return;
|
||||
}
|
||||
if (distance(mSecondPointerDown, /* move */ event) > mSwipeMinDistance) {
|
||||
// The second pointer is swiping.
|
||||
completeGesture(event, rawEvent, policyFlags);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
|
||||
cancelGesture(event, rawEvent, policyFlags);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
|
||||
cancelGesture(event, rawEvent, policyFlags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
if (mFirstPointerDown != null) {
|
||||
mFirstPointerDown.recycle();
|
||||
mFirstPointerDown = null;
|
||||
}
|
||||
if (mSecondPointerDown != null) {
|
||||
mSecondPointerDown.recycle();
|
||||
mSecondPointerDown = null;
|
||||
}
|
||||
super.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getGestureName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
private static double distance(@NonNull MotionEvent downEvent, @NonNull MotionEvent moveEvent) {
|
||||
final int downActionIndex = downEvent.getActionIndex();
|
||||
final int downPointerId = downEvent.getPointerId(downActionIndex);
|
||||
final int moveActionIndex = moveEvent.findPointerIndex(downPointerId);
|
||||
if (moveActionIndex < 0) {
|
||||
return -1;
|
||||
}
|
||||
return MathUtils.dist(downEvent.getX(downActionIndex), downEvent.getY(downActionIndex),
|
||||
moveEvent.getX(moveActionIndex), moveEvent.getY(moveActionIndex));
|
||||
}
|
||||
}
|
||||
@@ -323,7 +323,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl
|
||||
* manipulate the window magnifier or want to interact with current UI. The rule of leaving
|
||||
* this state is as follows:
|
||||
* <ol>
|
||||
* <li> If {@link MagnificationGestureMatcher#GESTURE_TWO_FINGER_DOWN} is detected,
|
||||
* <li> If {@link MagnificationGestureMatcher#GESTURE_TWO_FINGERS_DOWN_OR_SWIPE} is detected,
|
||||
* {@link State} will be transited to {@link PanningScalingGestureState}.</li>
|
||||
* <li> If other gesture is detected and the last motion event is neither ACTION_UP nor
|
||||
* ACTION_CANCEL.
|
||||
@@ -357,7 +357,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl
|
||||
new SimpleSwipe(context),
|
||||
multiTap,
|
||||
multiTapAndHold,
|
||||
new TwoFingersDown(context));
|
||||
new TwoFingersDownOrSwipe(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -399,7 +399,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl
|
||||
Slog.d(mLogTag,
|
||||
"onGestureDetected : delayedEventQueue = " + delayedEventQueue);
|
||||
}
|
||||
if (gestureId == MagnificationGestureMatcher.GESTURE_TWO_FINGER_DOWN
|
||||
if (gestureId == MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE
|
||||
&& mWindowMagnificationMgr.pointersInWindow(mDisplayId, motionEvent) > 0) {
|
||||
transitionTo(mObservePanningScalingState);
|
||||
} else if (gestureId == MagnificationGestureMatcher.GESTURE_TRIPLE_TAP) {
|
||||
|
||||
@@ -69,7 +69,7 @@ public class MagnificationGesturesObserverTest {
|
||||
mContext = InstrumentationRegistry.getContext();
|
||||
mInstrumentation = InstrumentationRegistry.getInstrumentation();
|
||||
mObserver = new MagnificationGesturesObserver(mCallback, new SimpleSwipe(mContext),
|
||||
new TwoFingersDown(mContext));
|
||||
new TwoFingersDownOrSwipe(mContext));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -77,9 +77,7 @@ public class MagnificationGesturesObserverTest {
|
||||
final MotionEvent moveEvent = TouchEventGenerator.moveEvent(Display.DEFAULT_DISPLAY,
|
||||
DEFAULT_X , DEFAULT_Y);
|
||||
|
||||
mInstrumentation.runOnMainSync(() -> {
|
||||
mObserver.onMotionEvent(moveEvent, moveEvent, 0);
|
||||
});
|
||||
mObserver.onMotionEvent(moveEvent, moveEvent, 0);
|
||||
|
||||
verify(mCallback).onGestureCancelled(eq(0L),
|
||||
mEventInfoArgumentCaptor.capture(), argThat(new MotionEventMatcher(moveEvent)));
|
||||
@@ -92,9 +90,7 @@ public class MagnificationGesturesObserverTest {
|
||||
final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY,
|
||||
DEFAULT_X , DEFAULT_Y);
|
||||
|
||||
mInstrumentation.runOnMainSync(() -> {
|
||||
mObserver.onMotionEvent(downEvent, downEvent, 0);
|
||||
});
|
||||
mObserver.onMotionEvent(downEvent, downEvent, 0);
|
||||
|
||||
verify(mCallback).onGestureCancelled(eq(0L),
|
||||
mEventInfoArgumentCaptor.capture(), argThat(new MotionEventMatcher(downEvent)));
|
||||
@@ -108,9 +104,7 @@ public class MagnificationGesturesObserverTest {
|
||||
final int timeoutMillis = MagnificationGestureMatcher.getMagnificationMultiTapTimeout(
|
||||
mContext) + 100;
|
||||
|
||||
mInstrumentation.runOnMainSync(() -> {
|
||||
mObserver.onMotionEvent(downEvent, downEvent, 0);
|
||||
});
|
||||
mObserver.onMotionEvent(downEvent, downEvent, 0);
|
||||
|
||||
verify(mCallback, timeout(timeoutMillis)).onGestureCancelled(eq(downEvent.getDownTime()),
|
||||
mEventInfoArgumentCaptor.capture(), argThat(new MotionEventMatcher(downEvent)));
|
||||
@@ -121,14 +115,12 @@ public class MagnificationGesturesObserverTest {
|
||||
public void sendEventsOfSwiping_onGestureCompleted() {
|
||||
final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY,
|
||||
DEFAULT_X, DEFAULT_Y);
|
||||
final float swipeDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
|
||||
final float swipeDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1;
|
||||
final MotionEvent moveEvent = TouchEventGenerator.moveEvent(Display.DEFAULT_DISPLAY,
|
||||
DEFAULT_X + swipeDistance, DEFAULT_Y + swipeDistance);
|
||||
|
||||
mInstrumentation.runOnMainSync(() -> {
|
||||
mObserver.onMotionEvent(downEvent, downEvent, 0);
|
||||
mObserver.onMotionEvent(moveEvent, moveEvent, 0);
|
||||
});
|
||||
mObserver.onMotionEvent(downEvent, downEvent, 0);
|
||||
mObserver.onMotionEvent(moveEvent, moveEvent, 0);
|
||||
|
||||
verify(mCallback).onGestureCompleted(eq(MagnificationGestureMatcher.GESTURE_SWIPE),
|
||||
eq(downEvent.getDownTime()), mEventInfoArgumentCaptor.capture(),
|
||||
|
||||
@@ -78,7 +78,7 @@ public class SimpleSwipeTest {
|
||||
|
||||
@Test
|
||||
public void sendSwipeEvent_onGestureCompleted() {
|
||||
final float swipeDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
|
||||
final float swipeDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1;
|
||||
final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY,
|
||||
DEFAULT_X, DEFAULT_Y);
|
||||
final MotionEvent moveEvent = TouchEventGenerator.moveEvent(Display.DEFAULT_DISPLAY,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
* Copyright (C) 2021 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.
|
||||
@@ -16,14 +16,20 @@
|
||||
|
||||
package com.android.server.accessibility.magnification;
|
||||
|
||||
import static com.android.server.accessibility.utils.TouchEventGenerator.movePointer;
|
||||
import static com.android.server.accessibility.utils.TouchEventGenerator.twoPointersDownEvents;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.after;
|
||||
import static org.mockito.Mockito.timeout;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PointF;
|
||||
import android.view.Display;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
@@ -35,18 +41,20 @@ import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Tests for {@link TwoFingersDown}.
|
||||
* Tests for {@link TwoFingersDownOrSwipe}.
|
||||
*/
|
||||
public class TwoFingersDownTest {
|
||||
public class TwoFingersDownOrSwipeTest {
|
||||
|
||||
private static final float DEFAULT_X = 100f;
|
||||
private static final float DEFAULT_Y = 100f;
|
||||
|
||||
private static Context sContext;
|
||||
private static float sSwipeMinDistance;
|
||||
private static int sTimeoutMillis;
|
||||
private static Context sContext;
|
||||
|
||||
private Context mContext;
|
||||
private GesturesObserver mGesturesObserver;
|
||||
@Mock
|
||||
private GesturesObserver.Listener mListener;
|
||||
@@ -56,13 +64,13 @@ public class TwoFingersDownTest {
|
||||
sContext = InstrumentationRegistry.getContext();
|
||||
sTimeoutMillis = MagnificationGestureMatcher.getMagnificationMultiTapTimeout(
|
||||
sContext) + 100;
|
||||
sSwipeMinDistance = ViewConfiguration.get(sContext).getScaledTouchSlop() + 1;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = InstrumentationRegistry.getContext();
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mGesturesObserver = new GesturesObserver(mListener, new TwoFingersDown(mContext));
|
||||
mGesturesObserver = new GesturesObserver(mListener, new TwoFingersDownOrSwipe(sContext));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -78,24 +86,16 @@ public class TwoFingersDownTest {
|
||||
|
||||
@Test
|
||||
public void sendTwoFingerDownEvent_onGestureCompleted() {
|
||||
final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY,
|
||||
DEFAULT_X, DEFAULT_Y);
|
||||
final MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords();
|
||||
defPointerCoords.x = DEFAULT_X;
|
||||
defPointerCoords.y = DEFAULT_Y;
|
||||
final MotionEvent.PointerCoords secondPointerCoords = new MotionEvent.PointerCoords();
|
||||
secondPointerCoords.x = DEFAULT_X + 10;
|
||||
secondPointerCoords.y = DEFAULT_Y + 10;
|
||||
final List<MotionEvent> downEvents = twoPointersDownEvents(Display.DEFAULT_DISPLAY,
|
||||
new PointF(DEFAULT_X, DEFAULT_Y), new PointF(DEFAULT_X + 10, DEFAULT_Y + 10));
|
||||
|
||||
final MotionEvent twoPointersDownEvent = TouchEventGenerator.twoPointersDownEvent(
|
||||
Display.DEFAULT_DISPLAY, defPointerCoords, secondPointerCoords);
|
||||
|
||||
mGesturesObserver.onMotionEvent(downEvent, downEvent, 0);
|
||||
mGesturesObserver.onMotionEvent(twoPointersDownEvent, twoPointersDownEvent, 0);
|
||||
for (MotionEvent event : downEvents) {
|
||||
mGesturesObserver.onMotionEvent(event, event, 0);
|
||||
}
|
||||
|
||||
verify(mListener, timeout(sTimeoutMillis)).onGestureCompleted(
|
||||
MagnificationGestureMatcher.GESTURE_TWO_FINGER_DOWN, twoPointersDownEvent,
|
||||
twoPointersDownEvent, 0);
|
||||
MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE, downEvents.get(1),
|
||||
downEvents.get(1), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -108,7 +108,39 @@ public class TwoFingersDownTest {
|
||||
mGesturesObserver.onMotionEvent(downEvent, downEvent, 0);
|
||||
mGesturesObserver.onMotionEvent(upEvent, upEvent, 0);
|
||||
|
||||
verify(mListener, timeout(sTimeoutMillis)).onGestureCancelled(any(MotionEvent.class),
|
||||
any(MotionEvent.class), eq(0));
|
||||
verify(mListener, after(ViewConfiguration.getDoubleTapTimeout())).onGestureCancelled(
|
||||
any(MotionEvent.class), any(MotionEvent.class), eq(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void firstPointerMove_twoPointersDown_onGestureCompleted() {
|
||||
final List<MotionEvent> downEvents = twoPointersDownEvents(Display.DEFAULT_DISPLAY,
|
||||
new PointF(DEFAULT_X, DEFAULT_Y), new PointF(DEFAULT_X + 10, DEFAULT_Y + 10));
|
||||
for (MotionEvent event : downEvents) {
|
||||
mGesturesObserver.onMotionEvent(event, event, 0);
|
||||
}
|
||||
final MotionEvent moveEvent = movePointer(downEvents.get(1), 0, sSwipeMinDistance, 0);
|
||||
|
||||
mGesturesObserver.onMotionEvent(moveEvent, moveEvent, 0);
|
||||
|
||||
verify(mListener).onGestureCompleted(
|
||||
MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE, moveEvent,
|
||||
moveEvent, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void secondPointerMove_twoPointersDown_onGestureCompleted() {
|
||||
final List<MotionEvent> downEvents = twoPointersDownEvents(Display.DEFAULT_DISPLAY,
|
||||
new PointF(DEFAULT_X, DEFAULT_Y), new PointF(DEFAULT_X + 10, DEFAULT_Y + 10));
|
||||
for (MotionEvent event : downEvents) {
|
||||
mGesturesObserver.onMotionEvent(event, event, 0);
|
||||
}
|
||||
final MotionEvent moveEvent = movePointer(downEvents.get(1), 1, sSwipeMinDistance, 0);
|
||||
|
||||
mGesturesObserver.onMotionEvent(moveEvent, moveEvent, 0);
|
||||
|
||||
verify(mListener).onGestureCompleted(
|
||||
MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE, moveEvent,
|
||||
moveEvent, 0);
|
||||
}
|
||||
}
|
||||
@@ -24,13 +24,15 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.os.RemoteException;
|
||||
import android.util.DebugUtils;
|
||||
import android.view.InputDevice;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.accessibility.EventStreamTransformation;
|
||||
@@ -43,6 +45,7 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
/**
|
||||
@@ -75,7 +78,7 @@ public class WindowMagnificationGestureHandlerTest {
|
||||
@Before
|
||||
public void setUp() throws RemoteException {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = InstrumentationRegistry.getContext();
|
||||
mContext = InstrumentationRegistry.getInstrumentation().getContext();
|
||||
mWindowMagnificationManager = new WindowMagnificationManager(mContext, 0,
|
||||
mock(WindowMagnificationManager.Callback.class));
|
||||
mMockConnection = new MockWindowMagnificationConnection();
|
||||
@@ -100,8 +103,8 @@ public class WindowMagnificationGestureHandlerTest {
|
||||
* Covers following paths to get to and back between each state and {@link #STATE_IDLE}.
|
||||
* <p>
|
||||
* <br> IDLE -> SHOW_MAGNIFIER [label="a11y\nbtn"]
|
||||
* <br> SHOW_MAGNIFIER -> TWO_FINGER_DOWN [label="2hold"]
|
||||
* <br> TWO_FINGER_DOWN -> SHOW_MAGNIFIER [label="release"]
|
||||
* <br> SHOW_MAGNIFIER -> TWO_FINGERS_DOWN [label="2hold"]
|
||||
* <br> TWO_FINGERS_DOWN -> SHOW_MAGNIFIER [label="release"]
|
||||
* <br> SHOW_MAGNIFIER -> IDLE [label="a11y\nbtn"]
|
||||
* <br> IDLE -> SHOW_MAGNIFIER_TRIPLE_TAP [label="3tap"]
|
||||
* <br> SHOW_MAGNIFIER_TRIPLE_TAP -> IDLE [label="3tap"]
|
||||
@@ -112,18 +115,16 @@ public class WindowMagnificationGestureHandlerTest {
|
||||
*/
|
||||
@Test
|
||||
public void testEachState_isReachableAndRecoverable() {
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
|
||||
forEachState(state -> {
|
||||
goFromStateIdleTo(state);
|
||||
assertIn(state);
|
||||
returnToNormalFrom(state);
|
||||
try {
|
||||
assertIn(STATE_IDLE);
|
||||
} catch (AssertionError e) {
|
||||
throw new AssertionError("Failed while testing state " + stateToString(state),
|
||||
e);
|
||||
}
|
||||
});
|
||||
forEachState(state -> {
|
||||
goFromStateIdleTo(state);
|
||||
assertIn(state);
|
||||
returnToNormalFrom(state);
|
||||
try {
|
||||
assertIn(STATE_IDLE);
|
||||
} catch (AssertionError e) {
|
||||
throw new AssertionError("Failed while testing state " + stateToString(state),
|
||||
e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -209,10 +210,19 @@ public class WindowMagnificationGestureHandlerTest {
|
||||
case STATE_TWO_FINGERS_DOWN: {
|
||||
goFromStateIdleTo(STATE_SHOW_MAGNIFIER_SHORTCUT);
|
||||
final Rect frame = mMockConnection.getMirrorWindowFrame();
|
||||
send(downEvent(frame.centerX(), frame.centerY()));
|
||||
//Second finger is outside the window.
|
||||
send(twoPointerDownEvent(new float[]{frame.centerX(), frame.centerX() + 10},
|
||||
new float[]{frame.centerY(), frame.centerY() + 10}));
|
||||
final PointF firstPointerDown = new PointF(frame.centerX(), frame.centerY());
|
||||
// The second finger is outside the window.
|
||||
final PointF secondPointerDown = new PointF(frame.right + 10,
|
||||
frame.bottom + 10);
|
||||
final List<MotionEvent> motionEvents =
|
||||
TouchEventGenerator.twoPointersDownEvents(DISPLAY_0,
|
||||
firstPointerDown, secondPointerDown);
|
||||
for (MotionEvent downEvent: motionEvents) {
|
||||
send(downEvent);
|
||||
}
|
||||
// Wait for two-finger down gesture completed.
|
||||
Thread.sleep(ViewConfiguration.getDoubleTapTimeout());
|
||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
|
||||
}
|
||||
break;
|
||||
case STATE_SHOW_MAGNIFIER_TRIPLE_TAP: {
|
||||
@@ -301,16 +311,6 @@ public class WindowMagnificationGestureHandlerTest {
|
||||
send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y));
|
||||
}
|
||||
|
||||
private MotionEvent twoPointerDownEvent(float[] x, float[] y) {
|
||||
final MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords();
|
||||
defPointerCoords.x = x[0];
|
||||
defPointerCoords.y = y[0];
|
||||
final MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
|
||||
pointerCoords.x = x[1];
|
||||
pointerCoords.y = y[1];
|
||||
return TouchEventGenerator.twoPointersDownEvent(DISPLAY_0, defPointerCoords, pointerCoords);
|
||||
}
|
||||
|
||||
private String stateDump() {
|
||||
return "\nCurrent state dump:\n" + mWindowMagnificationGestureHandler.mCurrentState;
|
||||
}
|
||||
|
||||
@@ -19,16 +19,19 @@ package com.android.server.accessibility.utils;
|
||||
import static android.view.MotionEvent.ACTION_DOWN;
|
||||
import static android.view.MotionEvent.ACTION_MOVE;
|
||||
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
|
||||
import static android.view.MotionEvent.ACTION_POINTER_UP;
|
||||
import static android.view.MotionEvent.ACTION_UP;
|
||||
import static android.view.MotionEvent.PointerCoords;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.os.SystemClock;
|
||||
import android.view.InputDevice;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* generates {@link MotionEvent} with source {@link InputDevice#SOURCE_TOUCHSCREEN}
|
||||
*
|
||||
*/
|
||||
public class TouchEventGenerator {
|
||||
|
||||
@@ -39,44 +42,68 @@ public class TouchEventGenerator {
|
||||
public static MotionEvent moveEvent(int displayId, float x, float y) {
|
||||
return generateSingleTouchEvent(displayId, ACTION_MOVE, x, y);
|
||||
}
|
||||
|
||||
public static MotionEvent upEvent(int displayId, float x, float y) {
|
||||
return generateSingleTouchEvent(displayId, ACTION_UP, x, y);
|
||||
}
|
||||
|
||||
public static MotionEvent twoPointersDownEvent(int displayId, PointerCoords defPointerCoords,
|
||||
PointerCoords pointerCoords) {
|
||||
return generateTwoPointersEvent(displayId, ACTION_POINTER_DOWN, defPointerCoords,
|
||||
pointerCoords);
|
||||
}
|
||||
|
||||
private static MotionEvent generateSingleTouchEvent(int displayId, int action, float x,
|
||||
float y) {
|
||||
final long downTime = SystemClock.uptimeMillis();
|
||||
final MotionEvent ev = MotionEvent.obtain(downTime, downTime,
|
||||
action, x, y, 0);
|
||||
ev.setDisplayId(displayId);
|
||||
ev.setSource(InputDevice.SOURCE_TOUCHSCREEN);
|
||||
return ev;
|
||||
return generateMultiplePointersEvent(displayId, action, new PointF(x, y));
|
||||
}
|
||||
|
||||
private static MotionEvent generateTwoPointersEvent(int displayId, int action,
|
||||
PointerCoords defPointerCoords, PointerCoords pointerCoords) {
|
||||
final long downTime = SystemClock.uptimeMillis();
|
||||
MotionEvent.PointerProperties defPointerProperties = new MotionEvent.PointerProperties();
|
||||
defPointerProperties.id = 0;
|
||||
defPointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
|
||||
MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
|
||||
pointerProperties.id = 1;
|
||||
pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
|
||||
/**
|
||||
* Creates a list of {@link MotionEvent} with given pointers location.
|
||||
*
|
||||
* @param displayId the display id
|
||||
* @param pointF1 location on the screen of the second pointer.
|
||||
* @param pointF2 location on the screen of the second pointer.
|
||||
* @return a list of {@link MotionEvent} with {@link MotionEvent#ACTION_DOWN} and {@link
|
||||
* MotionEvent#ACTION_POINTER_DOWN}.
|
||||
*/
|
||||
public static List<MotionEvent> twoPointersDownEvents(int displayId, PointF pointF1,
|
||||
PointF pointF2) {
|
||||
final List<MotionEvent> downEvents = new ArrayList<>();
|
||||
final MotionEvent downEvent = generateMultiplePointersEvent(displayId,
|
||||
MotionEvent.ACTION_DOWN, pointF1);
|
||||
downEvents.add(downEvent);
|
||||
|
||||
final int actionIndex = 1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
|
||||
final int action = ACTION_POINTER_DOWN | actionIndex;
|
||||
|
||||
final MotionEvent twoPointersDownEvent = generateMultiplePointersEvent(displayId, action,
|
||||
pointF1, pointF2);
|
||||
downEvents.add(twoPointersDownEvent);
|
||||
return downEvents;
|
||||
}
|
||||
|
||||
private static MotionEvent generateMultiplePointersEvent(int displayId, int action,
|
||||
PointF... pointFs) {
|
||||
final int length = pointFs.length;
|
||||
final MotionEvent.PointerCoords[] pointerCoordsArray =
|
||||
new MotionEvent.PointerCoords[length];
|
||||
final MotionEvent.PointerProperties[] pointerPropertiesArray =
|
||||
new MotionEvent.PointerProperties[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
|
||||
pointerCoords.x = pointFs[i].x;
|
||||
pointerCoords.y = pointFs[i].y;
|
||||
pointerCoordsArray[i] = pointerCoords;
|
||||
|
||||
MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
|
||||
pointerProperties.id = i;
|
||||
pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
|
||||
pointerPropertiesArray[i] = pointerProperties;
|
||||
}
|
||||
|
||||
final long downTime = SystemClock.uptimeMillis();
|
||||
final MotionEvent ev = MotionEvent.obtain(
|
||||
/* downTime */ downTime,
|
||||
/* eventTime */ downTime,
|
||||
/* action */ action,
|
||||
/* pointerCount */ 2,
|
||||
/* pointerProperties */ new MotionEvent.PointerProperties[] {
|
||||
defPointerProperties, pointerProperties},
|
||||
/* pointerCoords */ new PointerCoords[] { defPointerCoords, pointerCoords },
|
||||
/* pointerCount */ length,
|
||||
/* pointerProperties */ pointerPropertiesArray,
|
||||
/* pointerCoords */ pointerCoordsArray,
|
||||
/* metaState */ 0,
|
||||
/* buttonState */ 0,
|
||||
/* xPrecision */ 1.0f,
|
||||
@@ -88,4 +115,65 @@ public class TouchEventGenerator {
|
||||
ev.setDisplayId(displayId);
|
||||
return ev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a move event that moves the pointer of the original event with given index.
|
||||
* The original event should not be up event and we don't support
|
||||
* {@link MotionEvent#ACTION_POINTER_UP} now.
|
||||
*
|
||||
* @param originalEvent the move or down event
|
||||
* @param pointerIndex the index of the pointer we want to move.
|
||||
* @param offsetX the offset in X coordinate.
|
||||
* @param offsetY the offset in Y coordinate.
|
||||
* @return a motion event with move action.
|
||||
*/
|
||||
public static MotionEvent movePointer(MotionEvent originalEvent, int pointerIndex,
|
||||
float offsetX, float offsetY) {
|
||||
if (originalEvent.getActionMasked() == ACTION_UP) {
|
||||
throw new IllegalArgumentException("No pointer is on the screen");
|
||||
}
|
||||
|
||||
if (originalEvent.getActionMasked() == ACTION_POINTER_UP) {
|
||||
throw new IllegalArgumentException("unsupported yet,please implement it first");
|
||||
}
|
||||
|
||||
final int pointerCount = originalEvent.getPointerCount();
|
||||
if (pointerIndex >= pointerCount) {
|
||||
throw new IllegalArgumentException(
|
||||
pointerIndex + "is not available with pointer count" + pointerCount);
|
||||
}
|
||||
final int action = MotionEvent.ACTION_MOVE;
|
||||
final MotionEvent.PointerProperties[] pp = new MotionEvent.PointerProperties[pointerCount];
|
||||
for (int i = 0; i < pointerCount; i++) {
|
||||
MotionEvent.PointerProperties pointerProperty = new MotionEvent.PointerProperties();
|
||||
originalEvent.getPointerProperties(i, pointerProperty);
|
||||
pp[i] = pointerProperty;
|
||||
}
|
||||
|
||||
final MotionEvent.PointerCoords[] pc = new MotionEvent.PointerCoords[pointerCount];
|
||||
for (int i = 0; i < pointerCount; i++) {
|
||||
MotionEvent.PointerCoords pointerCoord = new MotionEvent.PointerCoords();
|
||||
originalEvent.getPointerCoords(i, pointerCoord);
|
||||
pc[i] = pointerCoord;
|
||||
}
|
||||
pc[pointerIndex].x += offsetX;
|
||||
pc[pointerIndex].y += offsetY;
|
||||
final MotionEvent ev = MotionEvent.obtain(
|
||||
/* downTime */ originalEvent.getDownTime(),
|
||||
/* eventTime */ SystemClock.uptimeMillis(),
|
||||
/* action */ action,
|
||||
/* pointerCount */ 2,
|
||||
/* pointerProperties */ pp,
|
||||
/* pointerCoords */ pc,
|
||||
/* metaState */ 0,
|
||||
/* buttonState */ 0,
|
||||
/* xPrecision */ 1.0f,
|
||||
/* yPrecision */ 1.0f,
|
||||
/* deviceId */ 0,
|
||||
/* edgeFlags */ 0,
|
||||
/* source */ originalEvent.getSource(),
|
||||
/* flags */ originalEvent.getFlags());
|
||||
ev.setDisplayId(originalEvent.getDisplayId());
|
||||
return ev;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user