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

Commit d3443225 authored by Annie Chin's avatar Annie Chin
Browse files

Filter interception and handling of touch events.

Test: Execute repro steps described on bugs and ensure that Calculator
no longer crashes.

Fixes: 33396891
Fixes: 33430365

-DragLayout intercepting touch events while already in motion would
sometimes cause crashes.
-Remove unnecessary touchSlop check.
-Refactor DragLayout touch filtering to occur in tryCaptureView instead.

Change-Id: Ib4a90ab6fa54de33b9593fda6295a7be518a620b
parent b9ce4d03
Loading
Loading
Loading
Loading
+11 −17
Original line number Diff line number Diff line
@@ -230,7 +230,7 @@ public class Calculator extends Activity
    private final DragLayout.CloseCallback mCloseCallback = new DragLayout.CloseCallback() {
        @Override
        public void onClose() {
            popFragmentBackstack();
            removeHistoryFragment(FragmentTransaction.TRANSIT_NONE);
        }
    };

@@ -246,11 +246,10 @@ public class Calculator extends Activity
        }

        @Override
        public boolean shouldInterceptTouchEvent(MotionEvent event) {
            if (!mDragLayout.isOpen()) {
                return isViewTarget(mDisplayView, event);
            }
            return true;
        public boolean shouldCaptureView(View view, int x, int y) {
            return mDragLayout.isMoving()
                    || mDragLayout.isOpen()
                    || mDragLayout.isViewUnder(mDisplayView, x, y);
        }

        @Override
@@ -263,8 +262,6 @@ public class Calculator extends Activity
        }
    };

    private final Rect mHitRect = new Rect();

    private CalculatorState mCurrentState;
    private Evaluator mEvaluator;

@@ -613,7 +610,7 @@ public class Calculator extends Activity
            if (mDragLayout.isOpen()) {
                if (!mHistoryFragment.stopActionModeOrContextMenu()) {
                    mDragLayout.setClosed();
                    popFragmentBackstack();
                    removeHistoryFragment(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
                }
                return;
            }
@@ -735,12 +732,15 @@ public class Calculator extends Activity
        }
    }

    private void popFragmentBackstack() {
    private void removeHistoryFragment(int transit) {
        final FragmentManager manager = getFragmentManager();
        if (manager == null || manager.isDestroyed()) {
            return;
        }
        manager.popBackStack();
        manager.beginTransaction()
                .remove(mHistoryFragment)
                .setTransition(transit)
                .commit();
        manager.executePendingTransactions();
    }

@@ -1453,12 +1453,6 @@ public class Calculator extends Activity
        }
    }

    private boolean isViewTarget(View view, MotionEvent event) {
        mHitRect.set(0, 0, view.getWidth(), view.getHeight());
        mDragLayout.offsetDescendantRectToMyCoords(view, mHitRect);
        return mHitRect.contains((int) event.getX(), (int) event.getY());
    }

    /**
     * Since we only support LTR format, using the RTL comma does not make sense.
     */
+67 −39
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package com.android.calculator2;

import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.view.ViewCompat;
@@ -27,7 +29,9 @@ import android.view.View;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

public class DragLayout extends RelativeLayout {
@@ -44,15 +48,14 @@ public class DragLayout extends RelativeLayout {
    private final List<DragCallback> mDragCallbacks = new CopyOnWriteArrayList<>();
    private CloseCallback mCloseCallback;

    private final Map<Integer, PointF> mLastMotionPoints = new HashMap<>();
    private final Rect mHitRect = new Rect();

    private int mDraggingState = ViewDragHelper.STATE_IDLE;
    private int mDraggingBorder;
    private int mVerticalRange;
    private boolean mIsOpen;

    // Used to determine whether a touch event should be intercepted.
    private float mInitialDownX;
    private float mInitialDownY;

    public DragLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
@@ -107,46 +110,52 @@ public class DragLayout extends RelativeLayout {
        super.onRestoreInstanceState(state);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
    private void saveLastMotion(MotionEvent event) {
        final int action = event.getActionMasked();

        // Always handle the case of the touch gesture being complete.
        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            // Release the scroll.
            mDragHelper.cancel();
            return false; // Do not intercept touch event, let the child handle it
        }

        final float x = event.getX();
        final float y = event.getY();

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mInitialDownX = x;
                mInitialDownY = y;
            case MotionEvent.ACTION_POINTER_DOWN: {
                final int actionIndex = event.getActionIndex();
                final int pointerId = event.getPointerId(actionIndex);
                final PointF point = new PointF(event.getX(actionIndex), event.getY(actionIndex));
                mLastMotionPoints.put(pointerId, point);
                break;
            case MotionEvent.ACTION_MOVE:
                final float deltaX = Math.abs(x - mInitialDownX);
                final float deltaY = Math.abs(y - mInitialDownY);
                final int slop = mDragHelper.getTouchSlop();
                if (deltaY > slop && deltaY > deltaX) {
            }
            case MotionEvent.ACTION_MOVE: {
                for (int i = event.getPointerCount() - 1; i >= 0; --i) {
                    final int pointerId = event.getPointerId(i);
                    final PointF point = mLastMotionPoints.get(pointerId);
                    if (point != null) {
                        point.set(event.getX(i), event.getY(i));
                    }
                }
                break;
                } else {
                    return false;
            }
            case MotionEvent.ACTION_POINTER_UP: {
                final int actionIndex = event.getActionIndex();
                final int pointerId = event.getPointerId(actionIndex);
                mLastMotionPoints.remove(pointerId);
                break;
            }
        boolean doDrag = true;
        for (DragCallback c : mDragCallbacks) {
            doDrag &= c.shouldInterceptTouchEvent(event);
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL: {
                mLastMotionPoints.clear();
                break;
            }
        return doDrag && mDragHelper.shouldInterceptTouchEvent(event);
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        saveLastMotion(event);
        return mDragHelper.shouldInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        saveLastMotion(event);
        mDragHelper.processTouchEvent(event);
        return super.onTouchEvent(event);
        return true;
    }

    @Override
@@ -163,6 +172,12 @@ public class DragLayout extends RelativeLayout {
        mHistoryFrame.setVisibility(VISIBLE);
    }

    public boolean isViewUnder(View view, int x, int y) {
        view.getHitRect(mHitRect);
        offsetDescendantRectToMyCoords((View) view.getParent(), mHitRect);
        return mHitRect.contains(x, y);
    }

    public boolean isMoving() {
        return mDraggingState == ViewDragHelper.STATE_DRAGGING
                || mDraggingState == ViewDragHelper.STATE_SETTLING;
@@ -214,8 +229,8 @@ public class DragLayout extends RelativeLayout {
        // Animate the RecyclerView text.
        void whileDragging(float yFraction);

        // Whether we should intercept the touch event
        boolean shouldInterceptTouchEvent(MotionEvent event);
        // Whether we should allow the view to be dragged.
        boolean shouldCaptureView(View view, int x, int y);

        int getDisplayHeight();

@@ -266,8 +281,21 @@ public class DragLayout extends RelativeLayout {
        }

        @Override
        public boolean tryCaptureView(View view, int i) {
            return view.getId() == R.id.history_frame;
        public boolean tryCaptureView(View view, int pointerId) {
            final PointF point = mLastMotionPoints.get(pointerId);
            if (point == null) {
                return false;
            }

            final int x = (int) point.x;
            final int y = (int) point.y;

            for (DragCallback c : mDragCallbacks) {
                if (!c.shouldCaptureView(view, x, y)) {
                    return false;
                }
            }
            return true;
        }

        @Override
+5 −3
Original line number Diff line number Diff line
@@ -53,8 +53,10 @@ public class HistoryFragment extends Fragment {
                }

                @Override
                public boolean shouldInterceptTouchEvent(MotionEvent event) {
                    return !mRecyclerView.canScrollVertically(1);
                public boolean shouldCaptureView(View view, int x, int y) {
                    return view.getId() == R.id.history_frame
                            && mDragLayout.isViewUnder(view, x, y)
                            && !mRecyclerView.canScrollVertically(1 /* scrolling down */);
                }

                @Override
@@ -158,7 +160,7 @@ public class HistoryFragment extends Fragment {
            if (!EvaluatorStateUtils.isDisplayEmpty(mEvaluator) && !isResultLayout) {
                // Add the current expression as the first element in the list (the layout is
                // reversed and we want the current expression to be the last one in the
                // recyclerview).
                // RecyclerView).
                // If we are in the result state, the result will animate to the last history
                // element in the list and there will be no "Current Expression."
                mEvaluator.copyMainToHistory();