Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cc19bc2c authored by Jean Chen's avatar Jean Chen
Browse files

feat(MultiFingerMultiTap):Create a new DetectingState and ViewportDraggingState for feature

The new detecting state and new view port dragging state will overwrite the motion event of the current state, enabling the implementation of the two-finger triple tap feature.

NO_IFTTT=copy the code and add IFTTT

Bug: 297805269
Test: manual
Change-Id: I85c5ff7e170c8c46402b3e97c389ec1c4c48a37c
parent c8d99bd6
Loading
Loading
Loading
Loading
+218 −30
Original line number Diff line number Diff line
@@ -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,63 @@ 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() != 1) {
                        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:
                case ACTION_POINTER_UP: {
                    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 +705,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 +769,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                }
            }
        }
        // LINT.ThenChange(:viewport_dragging_state_with_multi_finger)

        private boolean isAlwaysOnMagnificationEnabled() {
            return mFullScreenMagnificationController.isAlwaysOnMagnificationEnabled();
@@ -732,7 +796,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 +855,158 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
        }
    }

    final class DetectingStateWithMultiFinger extends DetectingState {
        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
                            // 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: {
                    if (isActivated() && event.getPointerCount() == 2) {
                        storePointerDownLocation(mSecondPointerDownLocation, event);
                        mHandler.sendEmptyMessageDelayed(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE,
                                ViewConfiguration.getTapTimeout());
                    } else {
                        transitionToDelegatingStateAndClear();
                    }
                }
                break;
                case ACTION_POINTER_UP: {
                    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 (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 (
                        // 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))) {
                        transitionToDelegatingStateAndClear();

                    }
                }
                break;
            }
        }
        // LINT.ThenChange(:detecting_state)
    }

    /**
     * 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 +1041,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 +1156,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 +1204,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 +1249,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 +1278,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
            }
        }

        private void sendDelayedMotionEvents() {
        protected void sendDelayedMotionEvents() {
            if (mDelayedEventQueue == null) {
                return;
            }
@@ -1112,7 +1300,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 +1324,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 +1344,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
            clear();
        }

        private boolean isActivated() {
        protected boolean isActivated() {
            return mFullScreenMagnificationController.isActivated(mDisplayId);
        }