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

Commit 289bb6d5 authored by Ameer Armaly's avatar Ameer Armaly Committed by Android (Google) Code Review
Browse files

Merge "Implement second-finger double-tap."

parents 348606fb c76eb982
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -77,6 +77,8 @@ class GestureManifold implements GestureMatcher.StateChangeListener {
        // Start with double tap.
        mGestures.add(new MultiTap(context, 2, GESTURE_DOUBLE_TAP, this));
        mGestures.add(new MultiTapAndHold(context, 2, GESTURE_DOUBLE_TAP_AND_HOLD, this));
        // Second-finger double tap.
        mGestures.add(new SecondFingerMultiTap(context, 2, GESTURE_DOUBLE_TAP, this));
        // One-direction swipes.
        mGestures.add(new Swipe(context, RIGHT, GESTURE_SWIPE_RIGHT, this));
        mGestures.add(new Swipe(context, LEFT, GESTURE_SWIPE_LEFT, this));
+167 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.gestures;

import static android.view.MotionEvent.INVALID_POINTER_ID;

import android.content.Context;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.ViewConfiguration;

/**
 * This class matches second-finger multi-tap gestures. A second-finger multi-tap gesture is where
 * one finger is held down and a second finger executes the taps. The number of taps for each
 * instance is specified in the constructor.
 */
class SecondFingerMultiTap extends GestureMatcher {
    final int mTargetTaps;
    int mDoubleTapSlop;
    int mTouchSlop;
    int mTapTimeout;
    int mDoubleTapTimeout;
    int mCurrentTaps;
    int mSecondFingerPointerId;
    float mBaseX;
    float mBaseY;

    SecondFingerMultiTap(
            Context context, int taps, int gesture, GestureMatcher.StateChangeListener listener) {
        super(gesture, new Handler(context.getMainLooper()), listener);
        mTargetTaps = taps;
        mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        mTapTimeout = ViewConfiguration.getTapTimeout();
        mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
        clear();
    }

    @Override
    protected void clear() {
        mCurrentTaps = 0;
        mBaseX = Float.NaN;
        mBaseY = Float.NaN;
        mSecondFingerPointerId = INVALID_POINTER_ID;
        super.clear();
    }

    @Override
    protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        if (event.getPointerCount() > 2) {
            cancelGesture(event, rawEvent, policyFlags);
            return;
        }
        // Second finger has gone down.
        int index = getActionIndex(event);
        mSecondFingerPointerId = event.getPointerId(index);
        cancelAfterTapTimeout(event, rawEvent, policyFlags);
        if (Float.isNaN(mBaseX) && Float.isNaN(mBaseY)) {
            mBaseX = event.getX();
            mBaseY = event.getY();
        }
        if (!isSecondFingerInsideSlop(rawEvent, mDoubleTapSlop)) {
            cancelGesture(event, rawEvent, policyFlags);
        }
        mBaseX = event.getX();
        mBaseY = event.getY();
    }

    @Override
    protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        if (event.getPointerCount() > 2) {
            cancelGesture(event, rawEvent, policyFlags);
            return;
        }
        cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
        if (!isSecondFingerInsideSlop(rawEvent, mTouchSlop)) {
            cancelGesture(event, rawEvent, policyFlags);
        }
        if (getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) {
            mCurrentTaps++;
            if (mCurrentTaps == mTargetTaps) {
                // Done.
                completeGesture(event, rawEvent, policyFlags);
                return;
            }
            // Needs more taps.
            cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
        } else {
            // Nonsensical event stream.
            cancelGesture(event, rawEvent, policyFlags);
        }
    }

    @Override
    protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        switch (event.getPointerCount()) {
            case 1:
                // We don't need to track anything about one-finger movements.
                break;
            case 2:
                if (!isSecondFingerInsideSlop(rawEvent, mTouchSlop)) {
                    cancelGesture(event, rawEvent, policyFlags);
                }
                break;
            default:
                // More than two fingers means we stop tracking.
                cancelGesture(event, rawEvent, policyFlags);
                break;
        }
    }

    @Override
    public String getGestureName() {
        switch (mTargetTaps) {
            case 2:
                return "Second Finger Double Tap";
            case 3:
                return "Second Finger Triple Tap";
            default:
                return "Second Finger " + Integer.toString(mTargetTaps) + " Taps";
        }
    }

    private boolean isSecondFingerInsideSlop(MotionEvent rawEvent, int slop) {
        int pointerIndex = rawEvent.findPointerIndex(mSecondFingerPointerId);
        if (pointerIndex == -1) {
            return false;
        }
        final float deltaX = mBaseX - rawEvent.getX(pointerIndex);
        final float deltaY = mBaseY - rawEvent.getY(pointerIndex);
        if (deltaX == 0 && deltaY == 0) {
            return true;
        }
        final double moveDelta = Math.hypot(deltaX, deltaY);
        return moveDelta <= slop;
    }

    private int getActionIndex(MotionEvent event) {
        return event.getAction()
                & MotionEvent.ACTION_POINTER_INDEX_MASK << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
    }

    @Override
    public String toString() {
        return super.toString()
                + ", Taps:"
                + mCurrentTaps
                + ", mBaseX: "
                + Float.toString(mBaseX)
                + ", mBaseY: "
                + Float.toString(mBaseY);
    }
}
+12 −14
Original line number Diff line number Diff line
@@ -286,11 +286,6 @@ public class TouchExplorer extends BaseEventStreamTransformation

    @Override
    public void onDoubleTapAndHold() {
        // Ignore the event if we aren't touch interacting.
        if (!mState.isTouchInteracting()) {
            return;
        }

        // Pointers should not be zero when running this command.
        if (mState.getLastReceivedEvent().getPointerCount() == 0) {
            return;
@@ -304,10 +299,6 @@ public class TouchExplorer extends BaseEventStreamTransformation

    @Override
    public boolean onDoubleTap() {
        if (!mState.isTouchInteracting()) {
            return false;
        }

        mAms.onTouchInteractionEnd();
        // Remove pending event deliveries.
        mSendHoverEnterAndMoveDelayed.cancel();
@@ -454,7 +445,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
                handleActionDown(event, rawEvent, policyFlags);
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                handleActionPointerDown();
                handleActionPointerDown(event, rawEvent, policyFlags);
                break;
            case MotionEvent.ACTION_MOVE:
                handleActionMoveStateTouchInteracting(event, rawEvent, policyFlags);
@@ -479,7 +470,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
                // We should have already received ACTION_DOWN. Ignore.
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                handleActionPointerDown();
                handleActionPointerDown(event, rawEvent, policyFlags);
                break;
            case MotionEvent.ACTION_MOVE:
                handleActionMoveStateTouchExploring(event, rawEvent, policyFlags);
@@ -496,12 +487,19 @@ public class TouchExplorer extends BaseEventStreamTransformation
     * Handles ACTION_POINTER_DOWN when in the touch exploring state. This event represents an
     * additional finger touching the screen.
     */
    private void handleActionPointerDown() {
    private void handleActionPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        // Another finger down means that if we have not started to deliver
        // hover events, we will not have to. The code for ACTION_MOVE will
        // decide what we will actually do next.

        if (mSendHoverEnterAndMoveDelayed.isPending()) {
            mSendHoverEnterAndMoveDelayed.cancel();
            mSendHoverExitDelayed.cancel();
        } else {
            // We have already delivered at least one hover event, so send hover exit to keep the
            // stream consistent.
            sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
        }
    }
    /**
     * Handles ACTION_MOVE while in the touch interacting state. This is where transitions to