diff options
2 files changed, 374 insertions, 30 deletions
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index 45ca7260bbe7..d31b1efafcc4 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -69,6 +69,7 @@ import com.android.internal.accessibility.util.AccessibilityStatsLogUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.accessibility.AccessibilityTraceManager; +import com.android.server.accessibility.Flags; import com.android.server.accessibility.gestures.GestureUtils; /** @@ -261,8 +262,12 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } mDelegatingState = new DelegatingState(); - mDetectingState = new DetectingState(context); - mViewportDraggingState = new ViewportDraggingState(); + mDetectingState = Flags.enableMagnificationMultipleFingerMultipleTapGesture() + ? new DetectingStateWithMultiFinger(context) + : new DetectingState(context); + mViewportDraggingState = Flags.enableMagnificationMultipleFingerMultipleTapGesture() + ? new ViewportDraggingStateWithMultiFinger() + : new ViewportDraggingState(); mPanningScalingState = new PanningScalingState(context); mSinglePanningState = new SinglePanningState(context); mFullScreenMagnificationVibrationHelper = fullScreenMagnificationVibrationHelper; @@ -634,6 +639,62 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } } + final class ViewportDraggingStateWithMultiFinger extends ViewportDraggingState { + // LINT.IfChange(viewport_dragging_state_with_multi_finger) + @Override + public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) + throws GestureException { + final int action = event.getActionMasked(); + switch (action) { + case ACTION_POINTER_DOWN: { + clearAndTransitToPanningScalingState(); + } + break; + case ACTION_MOVE: { + if (event.getPointerCount() > 2) { + throw new GestureException("Should have one pointer down."); + } + final float eventX = event.getX(); + final float eventY = event.getY(); + if (mFullScreenMagnificationController.magnificationRegionContains( + mDisplayId, eventX, eventY)) { + mFullScreenMagnificationController.setCenter(mDisplayId, eventX, eventY, + /* animate */ mLastMoveOutsideMagnifiedRegion, + AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + mLastMoveOutsideMagnifiedRegion = false; + } else { + mLastMoveOutsideMagnifiedRegion = true; + } + } + break; + + case ACTION_UP: + case ACTION_CANCEL: { + // If mScaleToRecoverAfterDraggingEnd >= 1.0, the dragging state is triggered + // by zoom in temporary, and the magnifier needs to recover to original scale + // after exiting dragging state. + // Otherwise, the magnifier should be disabled. + if (mScaleToRecoverAfterDraggingEnd >= 1.0f) { + zoomToScale(mScaleToRecoverAfterDraggingEnd, event.getX(), + event.getY()); + } else { + zoomOff(); + } + clear(); + mScaleToRecoverAfterDraggingEnd = Float.NaN; + transitionTo(mDetectingState); + } + break; + + case ACTION_DOWN: { + throw new GestureException( + "Unexpected event type: " + MotionEvent.actionToString(action)); + } + } + } + // LINT.ThenChange(:viewport_dragging_state) + } + /** * This class handles motion events when the event dispatcher has * determined that the user is performing a single-finger drag of the @@ -643,17 +704,18 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH * of the finger, and any part of the screen is reachable without lifting the finger. * This makes it the preferable mode for tasks like reading text spanning full screen width. */ - final class ViewportDraggingState implements State { + class ViewportDraggingState implements State { /** * The cached scale for recovering after dragging ends. * If the scale >= 1.0, the magnifier needs to recover to scale. * Otherwise, the magnifier should be disabled. */ - @VisibleForTesting float mScaleToRecoverAfterDraggingEnd = Float.NaN; + @VisibleForTesting protected float mScaleToRecoverAfterDraggingEnd = Float.NaN; - private boolean mLastMoveOutsideMagnifiedRegion; + protected boolean mLastMoveOutsideMagnifiedRegion; + // LINT.IfChange(viewport_dragging_state) @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) throws GestureException { @@ -706,6 +768,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } } } + // LINT.ThenChange(:viewport_dragging_state_with_multi_finger) private boolean isAlwaysOnMagnificationEnabled() { return mFullScreenMagnificationController.isAlwaysOnMagnificationEnabled(); @@ -732,7 +795,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH ? mFullScreenMagnificationController.getScale(mDisplayId) : Float.NaN; } - private void clearAndTransitToPanningScalingState() { + protected void clearAndTransitToPanningScalingState() { final float scaleToRecovery = mScaleToRecoverAfterDraggingEnd; clear(); mScaleToRecoverAfterDraggingEnd = scaleToRecovery; @@ -791,36 +854,220 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } } + final class DetectingStateWithMultiFinger extends DetectingState { + // A flag set to true when two fingers have touched down. + // Used to indicate what next finger action should be. + private boolean mIsTwoFingerCountReached = false; + // A tap counts when two fingers are down and up once. + private int mCompletedTapCount = 0; + DetectingStateWithMultiFinger(Context context) { + super(context); + } + + // LINT.IfChange(detecting_state_with_multi_finger) + @Override + public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + cacheDelayedMotionEvent(event, rawEvent, policyFlags); + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + mLastDetectingDownEventTime = event.getDownTime(); + mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); + + mFirstPointerDownLocation.set(event.getX(), event.getY()); + + if (!mFullScreenMagnificationController.magnificationRegionContains( + mDisplayId, event.getX(), event.getY())) { + + transitionToDelegatingStateAndClear(); + + } else if (isMultiTapTriggered(2 /* taps */)) { + + // 3tap and hold + afterLongTapTimeoutTransitionToDraggingState(event); + + } else if (isTapOutOfDistanceSlop()) { + + transitionToDelegatingStateAndClear(); + + } else if (mDetectSingleFingerTripleTap + || mDetectTwoFingerTripleTap + // If activated, delay an ACTION_DOWN for mMultiTapMaxDelay + // to ensure reachability of + // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN) + || isActivated()) { + + afterMultiTapTimeoutTransitionToDelegatingState(); + + } else { + + // Delegate pending events without delay + transitionToDelegatingStateAndClear(); + } + } + break; + case ACTION_POINTER_DOWN: { + mIsTwoFingerCountReached = mDetectTwoFingerTripleTap + && event.getPointerCount() == 2; + mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); + + if (isActivated() && event.getPointerCount() == 2) { + storePointerDownLocation(mSecondPointerDownLocation, event); + mHandler.sendEmptyMessageDelayed(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE, + ViewConfiguration.getTapTimeout()); + } else if (mIsTwoFingerCountReached) { + // Placing two-finger triple-taps behind isActivated to avoid + // blocking panning scaling state + if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event)) { + // 3tap and hold + afterLongTapTimeoutTransitionToDraggingState(event); + } else { + afterMultiTapTimeoutTransitionToDelegatingState(); + } + } else { + transitionToDelegatingStateAndClear(); + } + } + break; + case ACTION_POINTER_UP: { + // If it is a two-finger gesture, do not transition to the delegating state + // to ensure the reachability of + // the two-finger triple tap (triggerable with ACTION_MOVE and ACTION_UP) + if (!mIsTwoFingerCountReached) { + transitionToDelegatingStateAndClear(); + } + } + break; + case ACTION_MOVE: { + if (isFingerDown() + && distance(mLastDown, /* move */ event) > mSwipeMinDistance) { + // Swipe detected - transition immediately + + // For convenience, viewport dragging takes precedence + // over insta-delegating on 3tap&swipe + // (which is a rare combo to be used aside from magnification) + if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) { + transitionToViewportDraggingStateAndClear(event); + } else if (isActivated() && event.getPointerCount() == 2) { + if (mIsSinglePanningEnabled + && overscrollState(event, mFirstPointerDownLocation) + == OVERSCROLL_VERTICAL_EDGE) { + transitionToDelegatingStateAndClear(); + } + //Primary pointer is swiping, so transit to PanningScalingState + transitToPanningScalingStateAndClear(); + } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event) + && event.getPointerCount() == 2) { + // Placing two-finger triple-taps behind isActivated to avoid + // blocking panning scaling state + transitionToViewportDraggingStateAndClear(event); + } else if (mIsSinglePanningEnabled + && isActivated() + && event.getPointerCount() == 1) { + if (overscrollState(event, mFirstPointerDownLocation) + == OVERSCROLL_VERTICAL_EDGE) { + transitionToDelegatingStateAndClear(); + } + transitToSinglePanningStateAndClear(); + } else { + transitionToDelegatingStateAndClear(); + } + } else if (isActivated() && pointerDownValid(mSecondPointerDownLocation) + && distanceClosestPointerToPoint( + mSecondPointerDownLocation, /* move */ event) > mSwipeMinDistance) { + //Second pointer is swiping, so transit to PanningScalingState + transitToPanningScalingStateAndClear(); + } + } + break; + case ACTION_UP: { + + mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD); + + if (!mFullScreenMagnificationController.magnificationRegionContains( + mDisplayId, event.getX(), event.getY())) { + transitionToDelegatingStateAndClear(); + + } else if (isMultiTapTriggered(3 /* taps */)) { + onTripleTap(/* up */ event); + + } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 3, event)) { + onTripleTap(event); + + } else if ( + // Possible to be false on: 3tap&drag -> scale -> PTR_UP -> UP + isFingerDown() + //TODO long tap should never happen here + && ((timeBetween(mLastDown, mLastUp) >= mLongTapMinDelay) + || (distance(mLastDown, mLastUp) >= mSwipeMinDistance)) + // If it is a two-finger but not reach 3 tap, do not transition to the + // delegating state to ensure the reachability of the triple tap + && mCompletedTapCount == 0) { + transitionToDelegatingStateAndClear(); + + } + } + break; + } + } + // LINT.ThenChange(:detecting_state) + + @Override + public void clear() { + mCompletedTapCount = 0; + setShortcutTriggered(false); + removePendingDelayedMessages(); + clearDelayedMotionEvents(); + mFirstPointerDownLocation.set(Float.NaN, Float.NaN); + mSecondPointerDownLocation.set(Float.NaN, Float.NaN); + } + + private boolean isMultiFingerMultiTapTriggered(int targetTapCount, MotionEvent event) { + if (event.getActionMasked() == ACTION_UP && mIsTwoFingerCountReached) { + mCompletedTapCount++; + mIsTwoFingerCountReached = false; + } + return mDetectTwoFingerTripleTap && mCompletedTapCount == targetTapCount; + } + + void transitionToDelegatingStateAndClear() { + mCompletedTapCount = 0; + transitionTo(mDelegatingState); + sendDelayedMotionEvents(); + removePendingDelayedMessages(); + mFirstPointerDownLocation.set(Float.NaN, Float.NaN); + mSecondPointerDownLocation.set(Float.NaN, Float.NaN); + } + } + /** * This class handles motion events when the event dispatch has not yet * determined what the user is doing. It watches for various tap events. */ - final class DetectingState implements State, Handler.Callback { + class DetectingState implements State, Handler.Callback { - private static final int MESSAGE_ON_TRIPLE_TAP_AND_HOLD = 1; - private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2; - private static final int MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE = 3; + protected static final int MESSAGE_ON_TRIPLE_TAP_AND_HOLD = 1; + protected static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2; + protected static final int MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE = 3; final int mLongTapMinDelay; final int mSwipeMinDistance; final int mMultiTapMaxDelay; final int mMultiTapMaxDistance; - private MotionEventInfo mDelayedEventQueue; - MotionEvent mLastDown; - private MotionEvent mPreLastDown; - private MotionEvent mLastUp; - private MotionEvent mPreLastUp; - private PointF mSecondPointerDownLocation = new PointF(Float.NaN, Float.NaN); + protected MotionEventInfo mDelayedEventQueue; + protected MotionEvent mLastDown; + protected MotionEvent mPreLastDown; + protected MotionEvent mLastUp; + protected MotionEvent mPreLastUp; - private long mLastDetectingDownEventTime; + protected PointF mFirstPointerDownLocation = new PointF(Float.NaN, Float.NaN); + protected PointF mSecondPointerDownLocation = new PointF(Float.NaN, Float.NaN); + protected long mLastDetectingDownEventTime; @VisibleForTesting boolean mShortcutTriggered; @VisibleForTesting Handler mHandler = new Handler(Looper.getMainLooper(), this); - private PointF mFirstPointerDownLocation = new PointF(Float.NaN, Float.NaN); - DetectingState(Context context) { mLongTapMinDelay = ViewConfiguration.getLongPressTimeout(); mMultiTapMaxDelay = ViewConfiguration.getDoubleTapTimeout() @@ -855,6 +1102,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH return true; } + // LINT.IfChange(detecting_state) @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { cacheDelayedMotionEvent(event, rawEvent, policyFlags); @@ -969,23 +1217,24 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH break; } } + // LINT.ThenChange(:detecting_state_with_multi_finger) - private void storePointerDownLocation(PointF pointerDownLocation, MotionEvent event) { + protected void storePointerDownLocation(PointF pointerDownLocation, MotionEvent event) { final int index = event.getActionIndex(); pointerDownLocation.set(event.getX(index), event.getY(index)); } - private boolean pointerDownValid(PointF pointerDownLocation) { + protected boolean pointerDownValid(PointF pointerDownLocation) { return !(Float.isNaN(pointerDownLocation.x) && Float.isNaN( pointerDownLocation.y)); } - private void transitToPanningScalingStateAndClear() { + protected void transitToPanningScalingStateAndClear() { transitionTo(mPanningScalingState); clear(); } - private void transitToSinglePanningStateAndClear() { + protected void transitToSinglePanningStateAndClear() { transitionTo(mSinglePanningState); clear(); } @@ -1016,7 +1265,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH return mLastDown != null; } - private long timeBetween(@Nullable MotionEvent a, @Nullable MotionEvent b) { + protected long timeBetween(@Nullable MotionEvent a, @Nullable MotionEvent b) { if (a == null && b == null) return 0; return abs(timeOf(a) - timeOf(b)); } @@ -1061,13 +1310,13 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mSecondPointerDownLocation.set(Float.NaN, Float.NaN); } - private void removePendingDelayedMessages() { + protected void removePendingDelayedMessages() { mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD); mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); mHandler.removeMessages(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE); } - private void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent, + protected void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { if (event.getActionMasked() == ACTION_DOWN) { mPreLastDown = mLastDown; @@ -1090,7 +1339,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } } - private void sendDelayedMotionEvents() { + protected void sendDelayedMotionEvents() { if (mDelayedEventQueue == null) { return; } @@ -1112,7 +1361,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } while (mDelayedEventQueue != null); } - private void clearDelayedMotionEvents() { + protected void clearDelayedMotionEvents() { while (mDelayedEventQueue != null) { MotionEventInfo info = mDelayedEventQueue; mDelayedEventQueue = info.mNext; @@ -1136,7 +1385,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH * 1. direct three tap gesture * 2. one tap while shortcut triggered (it counts as two taps). */ - private void onTripleTap(MotionEvent up) { + protected void onTripleTap(MotionEvent up) { if (DEBUG_DETECTING) { Slog.i(mLogTag, "onTripleTap(); delayed: " + MotionEventInfo.toString(mDelayedEventQueue)); @@ -1156,7 +1405,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH clear(); } - private boolean isActivated() { + protected boolean isActivated() { return mFullScreenMagnificationController.isActivated(mDisplayId); } @@ -1167,6 +1416,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH // Only log the 3tap and hold event if (!shortcutTriggered) { + // TODO:(b/309534286): Add metrics for two-finger triple-tap and fix + // the log two-finger bug before enabling the flag // Triple tap and hold also belongs to triple tap event final boolean enabled = !isActivated(); mMagnificationLogger.logMagnificationTripleTap(enabled); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index a2e7cf3f1bb2..fd2cf6d4bb5f 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -55,6 +55,10 @@ import android.os.Message; import android.os.UserHandle; import android.os.VibrationEffect; import android.os.Vibrator; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.testing.TestableContext; import android.util.DebugUtils; @@ -69,6 +73,7 @@ import com.android.internal.util.ConcurrentUtils; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.accessibility.EventStreamTransformation; +import com.android.server.accessibility.Flags; import com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback; import com.android.server.testutils.OffsettableClock; import com.android.server.testutils.TestHandler; @@ -134,6 +139,9 @@ import java.util.function.IntConsumer; @RunWith(AndroidJUnit4.class) public class FullScreenMagnificationGestureHandlerTest { + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public static final int STATE_IDLE = 1; public static final int STATE_ACTIVATED = 2; public static final int STATE_SHORTCUT_TRIGGERED = 3; @@ -425,6 +433,7 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) public void testDisablingTripleTap_removesInputLag() { mMgh = newInstance(/* detectSingleFingerTripleTap */ false, /* detectTwoFingerTripleTap */ true, /* detectShortcut */ true); @@ -436,6 +445,18 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) + public void testDisablingSingleFingerTripleTapAndTwoFingerTripleTap_removesInputLag() { + mMgh = newInstance(/* detectSingleFingerTripleTap */ false, + /* detectTwoFingerTripleTap */ false, /* detectShortcut */ true); + goFromStateIdleTo(STATE_IDLE); + allowEventDelegation(); + tap(); + // no fast forward + verify(mMgh.getNext(), times(2)).onMotionEvent(any(), any(), anyInt()); + } + + @Test public void testLongTapAfterShortcutTriggered_neverLogMagnificationTripleTap() { goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED); @@ -510,6 +531,54 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) + public void testTwoFingerTripleTap_StateIsIdle_shouldInActivated() { + goFromStateIdleTo(STATE_IDLE); + + twoFingerTap(); + twoFingerTap(); + twoFingerTap(); + + assertIn(STATE_ACTIVATED); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) + public void testTwoFingerTripleTap_StateIsActivated_shouldInIdle() { + goFromStateIdleTo(STATE_ACTIVATED); + + twoFingerTap(); + twoFingerTap(); + twoFingerTap(); + + assertIn(STATE_IDLE); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) + public void testTwoFingerTripleTapAndHold_StateIsIdle_shouldZoomsImmediately() { + goFromStateIdleTo(STATE_IDLE); + + twoFingerTap(); + twoFingerTap(); + twoFingerTapAndHold(); + + assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) + public void testTwoFingerTripleSwipeAndHold_StateIsIdle_shouldZoomsImmediately() { + goFromStateIdleTo(STATE_IDLE); + + twoFingerTap(); + twoFingerTap(); + twoFingerSwipeAndHold(); + + assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP); + } + + @Test public void testMultiTap_outOfDistanceSlop_shouldInIdle() { // All delay motion events should be sent, if multi-tap with out of distance slop. // STATE_IDLE will check if tapCount() < 2. @@ -1258,6 +1327,30 @@ public class FullScreenMagnificationGestureHandlerTest { send(upEvent()); } + private void twoFingerTap() { + send(downEvent()); + send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y)); + send(pointerEvent(ACTION_POINTER_UP, DEFAULT_X * 2, DEFAULT_Y)); + send(upEvent()); + } + + private void twoFingerTapAndHold() { + send(downEvent()); + send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y)); + fastForward(2000); + } + + private void twoFingerSwipeAndHold() { + PointF pointer1 = DEFAULT_POINT; + PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y); + + send(downEvent()); + send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1)); + final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop(); + pointer1.offset(sWipeMinDistance + 1, 0); + send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0)); + } + private void triggerShortcut() { mMgh.notifyShortcutTriggered(); } |