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

Commit e33cef80 authored by Adam Powell's avatar Adam Powell
Browse files

Ensure WebView passes a complete/in-order event stream to

ScaleGestureDetector. Prerequisite for several ScaleGestureDetector
bugfixes.

Bugfixes for ScaleGestureDetector.

Make ScaleGestureDetector properly support >2 pointers. Track the most
recent two pointers for scale gestures.

Change-Id: I966319ad438c1c8e1103f245b53b3862c2d4e3c0
parent a28f6f11
Loading
Loading
Loading
Loading
+200 −54
Original line number Diff line number Diff line
@@ -154,6 +154,11 @@ public class ScaleGestureDetector {
    private float mBottomSlopEdge;
    private boolean mSloppyGesture;

    // Pointer IDs currently responsible for the two fingers controlling the gesture
    private int mActiveId0;
    private int mActiveId1;
    private boolean mActive0MostRecent;

    public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
        ViewConfiguration config = ViewConfiguration.get(context);
        mContext = context;
@@ -162,11 +167,21 @@ public class ScaleGestureDetector {
    }

    public boolean onTouchEvent(MotionEvent event) {
        final int action = event.getAction();
        final int action = event.getActionMasked();
        boolean handled = true;

        if (!mGestureInProgress) {
            switch (action & MotionEvent.ACTION_MASK) {
            switch (action) {
            case MotionEvent.ACTION_DOWN: {
                mActiveId0 = event.getPointerId(0);
                mActive0MostRecent = true;
            }
            break;

            case MotionEvent.ACTION_UP:
                reset();
                break;

            case MotionEvent.ACTION_POINTER_DOWN: {
                // We have a new multi-finger gesture

@@ -175,12 +190,15 @@ public class ScaleGestureDetector {
                mRightSlopEdge = metrics.widthPixels - mEdgeSlop;
                mBottomSlopEdge = metrics.heightPixels - mEdgeSlop;

                // Be paranoid in case we missed an event
                reset();

                if (mPrevEvent != null) mPrevEvent.recycle();
                mPrevEvent = MotionEvent.obtain(event);
                mTimeDelta = 0;

                int index1 = event.getActionIndex();
                int index0 = event.findPointerIndex(mActiveId0);
                mActiveId1 = event.getPointerId(index1);
                mActive0MostRecent = false;

                setContext(event);

                // Check if we have a sloppy gesture. If so, delay
@@ -190,10 +208,10 @@ public class ScaleGestureDetector {
                final float edgeSlop = mEdgeSlop;
                final float rightSlop = mRightSlopEdge;
                final float bottomSlop = mBottomSlopEdge;
                final float x0 = event.getRawX();
                final float y0 = event.getRawY();
                final float x1 = getRawX(event, 1);
                final float y1 = getRawY(event, 1);
                float x0 = getRawX(event, index0);
                float y0 = getRawY(event, index0);
                float x1 = getRawX(event, index1);
                float y1 = getRawY(event, index1);

                boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop
                        || x0 > rightSlop || y0 > bottomSlop;
@@ -205,14 +223,15 @@ public class ScaleGestureDetector {
                    mFocusY = -1;
                    mSloppyGesture = true;
                } else if (p0sloppy) {
                    mFocusX = event.getX(1);
                    mFocusY = event.getY(1);
                    mFocusX = event.getX(index1);
                    mFocusY = event.getY(index1);
                    mSloppyGesture = true;
                } else if (p1sloppy) {
                    mFocusX = event.getX(0);
                    mFocusY = event.getY(0);
                    mFocusX = event.getX(index0);
                    mFocusY = event.getY(index0);
                    mSloppyGesture = true;
                } else {
                    mSloppyGesture = false;
                    mGestureInProgress = mListener.onScaleBegin(this);
                }
            }
@@ -224,25 +243,51 @@ public class ScaleGestureDetector {
                    final float edgeSlop = mEdgeSlop;
                    final float rightSlop = mRightSlopEdge;
                    final float bottomSlop = mBottomSlopEdge;
                    final float x0 = event.getRawX();
                    final float y0 = event.getRawY();
                    final float x1 = getRawX(event, 1);
                    final float y1 = getRawY(event, 1);
                    int index0 = event.findPointerIndex(mActiveId0);
                    int index1 = event.findPointerIndex(mActiveId1);
                    float x0 = getRawX(event, index0);
                    float y0 = getRawY(event, index0);
                    float x1 = getRawX(event, index1);
                    float y1 = getRawY(event, index1);

                    boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop
                            || x0 > rightSlop || y0 > bottomSlop;
                    boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
                            || x1 > rightSlop || y1 > bottomSlop;

                    if (p0sloppy) {
                        // Do we have a different pointer that isn't sloppy?
                        int index = findNewActiveIndex(event, mActiveId1, index0);
                        if (index >= 0) {
                            index0 = index;
                            mActiveId0 = event.getPointerId(index);
                            x0 = getRawX(event, index);
                            y0 = getRawY(event, index);
                            p0sloppy = false;
                        }
                    }

                    if (p1sloppy) {
                        // Do we have a different pointer that isn't sloppy?
                        int index = findNewActiveIndex(event, mActiveId0, index1);
                        if (index >= 0) {
                            index1 = index;
                            mActiveId1 = event.getPointerId(index);
                            x1 = getRawX(event, index);
                            y1 = getRawY(event, index);
                            p1sloppy = false;
                        }
                    }

                    if(p0sloppy && p1sloppy) {
                        mFocusX = -1;
                        mFocusY = -1;
                    } else if (p0sloppy) {
                        mFocusX = event.getX(1);
                        mFocusY = event.getY(1);
                        mFocusX = event.getX(index1);
                        mFocusY = event.getY(index1);
                    } else if (p1sloppy) {
                        mFocusX = event.getX(0);
                        mFocusY = event.getY(0);
                        mFocusX = event.getX(index0);
                        mFocusY = event.getY(index0);
                    } else {
                        mSloppyGesture = false;
                        mGestureInProgress = mListener.onScaleBegin(this);
@@ -252,43 +297,109 @@ public class ScaleGestureDetector {

            case MotionEvent.ACTION_POINTER_UP:
                if (mSloppyGesture) {
                    final int pointerCount = event.getPointerCount();
                    final int actionIndex = event.getActionIndex();
                    final int actionId = event.getPointerId(actionIndex);

                    if (pointerCount > 2) {
                        if (actionId == mActiveId0) {
                            final int newIndex = findNewActiveIndex(event, mActiveId1, actionIndex);
                            if (newIndex >= 0) mActiveId0 = event.getPointerId(newIndex);
                        } else if (actionId == mActiveId1) {
                            final int newIndex = findNewActiveIndex(event, mActiveId0, actionIndex);
                            if (newIndex >= 0) mActiveId1 = event.getPointerId(newIndex);
                        }
                    } else {
                        // Set focus point to the remaining finger
                    int id = (((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
                            >> MotionEvent.ACTION_POINTER_INDEX_SHIFT) == 0) ? 1 : 0;
                    mFocusX = event.getX(id);
                    mFocusY = event.getY(id);
                        final int index = event.findPointerIndex(actionId == mActiveId0 ?
                                mActiveId1 : mActiveId0);
                        mActiveId0 = event.getPointerId(index);
                        mActive0MostRecent = true;
                        mActiveId1 = -1;
                        mFocusX = event.getX(index);
                        mFocusY = event.getY(index);
                    }
                }
                break;
            }
        } else {
            // Transform gesture in progress - attempt to handle it
            switch (action & MotionEvent.ACTION_MASK) {
                case MotionEvent.ACTION_POINTER_UP:
            switch (action) {
                case MotionEvent.ACTION_POINTER_DOWN: {
                    // End the old gesture and begin a new one with the most recent two fingers.
                    mListener.onScaleEnd(this);
                    final int oldActive0 = mActiveId0;
                    final int oldActive1 = mActiveId1;
                    reset();

                    mPrevEvent = MotionEvent.obtain(event);
                    mActiveId0 = mActive0MostRecent ? oldActive0 : oldActive1;
                    mActiveId1 = event.getPointerId(event.getActionIndex());
                    mActive0MostRecent = false;

                    setContext(event);

                    mGestureInProgress = mListener.onScaleBegin(this);
                }
                break;

                case MotionEvent.ACTION_POINTER_UP: {
                    final int pointerCount = event.getPointerCount();
                    final int actionIndex = event.getActionIndex();
                    final int actionId = event.getPointerId(actionIndex);

                    boolean gestureEnded = false;
                    if (pointerCount > 2) {
                        if (actionId == mActiveId0) {
                            final int newIndex = findNewActiveIndex(event, mActiveId1, actionIndex);
                            if (newIndex >= 0) {
                                mActiveId0 = event.getPointerId(newIndex);
                            } else {
                                gestureEnded = true;
                            }
                        } else if (actionId == mActiveId1) {
                            final int newIndex = findNewActiveIndex(event, mActiveId0, actionIndex);
                            if (newIndex >= 0) {
                                mActiveId1 = event.getPointerId(newIndex);
                            } else {
                                gestureEnded = true;
                            }
                        }
                        mPrevEvent.recycle();
                        mPrevEvent = MotionEvent.obtain(event);
                        setContext(event);
                    } else {
                        gestureEnded = true;
                    }

                    if (gestureEnded) {
                        // Gesture ended
                        setContext(event);

                        // Set focus point to the remaining finger
                    int id = (((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
                            >> MotionEvent.ACTION_POINTER_INDEX_SHIFT) == 0) ? 1 : 0;
                    mFocusX = event.getX(id);
                    mFocusY = event.getY(id);
                        final int activeId = actionId == mActiveId0 ? mActiveId1 : mActiveId0;
                        final int index = event.findPointerIndex(activeId);
                        mFocusX = event.getX(index);
                        mFocusY = event.getY(index);

                    if (!mSloppyGesture) {
                        mListener.onScaleEnd(this);
                    }

                        reset();
                        mActiveId0 = activeId;
                        mActive0MostRecent = true;
                    }
                }
                break;

                case MotionEvent.ACTION_CANCEL:
                    if (!mSloppyGesture) {
                    mListener.onScaleEnd(this);
                    }
                    reset();
                    break;

                case MotionEvent.ACTION_UP:
                    reset();
                    break;

                case MotionEvent.ACTION_MOVE:
                case MotionEvent.ACTION_MOVE: {
                    setContext(event);

                    // Only accept the event if our relative pressure is within
@@ -302,16 +413,43 @@ public class ScaleGestureDetector {
                            mPrevEvent = MotionEvent.obtain(event);
                        }
                    }
                }
                break;
            }
        }
        return handled;
    }

    private int findNewActiveIndex(MotionEvent ev, int otherActiveId, int oldIndex) {
        final int pointerCount = ev.getPointerCount();

        // It's ok if this isn't found and returns -1, it simply won't match.
        final int otherActiveIndex = ev.findPointerIndex(otherActiveId);
        int newActiveIndex = -1;

        // Pick a new id and update tracking state. Only pick pointers not on the slop edges.
        for (int i = 0; i < pointerCount; i++) {
            if (i != oldIndex && i != otherActiveIndex) {
                final float edgeSlop = mEdgeSlop;
                final float rightSlop = mRightSlopEdge;
                final float bottomSlop = mBottomSlopEdge;
                float x = getRawX(ev, i);
                float y = getRawY(ev, i);
                if (x >= edgeSlop && y >= edgeSlop && x <= rightSlop && y <= bottomSlop) {
                    newActiveIndex = i;
                    break;
                }
            }
        }

        return newActiveIndex;
    }

    /**
     * MotionEvent has no getRawX(int) method; simulate it pending future API approval.
     */
    private static float getRawX(MotionEvent event, int pointerIndex) {
        if (pointerIndex == 0) return event.getRawX();
        float offset = event.getRawX() - event.getX();
        return event.getX(pointerIndex) + offset;
    }
@@ -320,6 +458,7 @@ public class ScaleGestureDetector {
     * MotionEvent has no getRawY(int) method; simulate it pending future API approval.
     */
    private static float getRawY(MotionEvent event, int pointerIndex) {
        if (pointerIndex == 0) return event.getRawY();
        float offset = event.getRawY() - event.getY();
        return event.getY(pointerIndex) + offset;
    }
@@ -336,14 +475,19 @@ public class ScaleGestureDetector {

        final MotionEvent prev = mPrevEvent;

        final float px0 = prev.getX(0);
        final float py0 = prev.getY(0);
        final float px1 = prev.getX(1);
        final float py1 = prev.getY(1);
        final float cx0 = curr.getX(0);
        final float cy0 = curr.getY(0);
        final float cx1 = curr.getX(1);
        final float cy1 = curr.getY(1);
        final int prevIndex0 = prev.findPointerIndex(mActiveId0);
        final int prevIndex1 = prev.findPointerIndex(mActiveId1);
        final int currIndex0 = curr.findPointerIndex(mActiveId0);
        final int currIndex1 = curr.findPointerIndex(mActiveId1);

        final float px0 = prev.getX(prevIndex0);
        final float py0 = prev.getY(prevIndex0);
        final float px1 = prev.getX(prevIndex1);
        final float py1 = prev.getY(prevIndex1);
        final float cx0 = curr.getX(currIndex0);
        final float cy0 = curr.getY(currIndex0);
        final float cx1 = curr.getX(currIndex1);
        final float cy1 = curr.getY(currIndex1);

        final float pvx = px1 - px0;
        final float pvy = py1 - py0;
@@ -357,8 +501,8 @@ public class ScaleGestureDetector {
        mFocusX = cx0 + cvx * 0.5f;
        mFocusY = cy0 + cvy * 0.5f;
        mTimeDelta = curr.getEventTime() - prev.getEventTime();
        mCurrPressure = curr.getPressure(0) + curr.getPressure(1);
        mPrevPressure = prev.getPressure(0) + prev.getPressure(1);
        mCurrPressure = curr.getPressure(currIndex0) + curr.getPressure(currIndex1);
        mPrevPressure = prev.getPressure(prevIndex0) + prev.getPressure(prevIndex1);
    }

    private void reset() {
@@ -372,6 +516,8 @@ public class ScaleGestureDetector {
        }
        mSloppyGesture = false;
        mGestureInProgress = false;
        mActiveId0 = -1;
        mActiveId1 = -1;
    }

    /**
+9 −18
Original line number Diff line number Diff line
@@ -5414,12 +5414,20 @@ public class WebView extends AbsoluteLayout

            // If WebKit already showed no interests in this sequence of events,
            // WebView handles them directly.
            if (mPreventDefault == PREVENT_DEFAULT_NO && action == MotionEvent.ACTION_MOVE) {
            if (mPreventDefault == PREVENT_DEFAULT_NO) {
                handleMultiTouchInWebView(ev);
            } else {
                passMultiTouchToWebKit(ev);
            }
            return true;
        } else {
            final ScaleGestureDetector detector = mZoomManager.getMultiTouchGestureDetector();
            if (detector != null) {
                // ScaleGestureDetector needs a consistent event stream to operate properly.
                // It won't take any action with fewer than two pointers, but it needs to
                // update internal bookkeeping state.
                detector.onTouchEvent(ev);
            }
        }

        // Skip ACTION_MOVE for single touch if it's still handling multi-touch.
@@ -5959,23 +5967,6 @@ public class WebView extends AbsoluteLayout
        float x = ev.getX();
        float y = ev.getY();

        if (!detector.isInProgress() &&
            ev.getActionMasked() != MotionEvent.ACTION_POINTER_DOWN) {
            // Insert a fake pointer down event in order to start
            // the zoom scale detector.
            MotionEvent temp = MotionEvent.obtain(ev);
            // Clear the original event and set it to
            // ACTION_POINTER_DOWN.
            try {
                temp.setAction(temp.getAction() &
                ~MotionEvent.ACTION_MASK |
                MotionEvent.ACTION_POINTER_DOWN);
                detector.onTouchEvent(temp);
            } finally {
                temp.recycle();
            }
        }

        detector.onTouchEvent(ev);

        if (detector.isInProgress()) {