From 90ee05903dbfbfbfc919eed53ef62f3e93b1d412 Mon Sep 17 00:00:00 2001 From: Matt Casey Date: Fri, 9 Apr 2021 13:54:29 -0400 Subject: [PATCH] Prevent CropView from getting confused by multiple pointers. Not really implementing full multitouch, but ensuring that letting up on the first finger doesn't just continue the event with another one. Also refactor some of the MagnifierView's event listening to decouple it from CropView a bit more. Bug: 183240525 Test: Touch one edge with one finger, then put another one down and lift the first, ensure that movement doesn't continue with second finger. Change-Id: Ia9f0293a914fb85f80a746f1093a3b23b23eab73 --- .../android/systemui/screenshot/CropView.java | 86 ++++++++++++++----- .../systemui/screenshot/MagnifierView.java | 81 ++++++++--------- 2 files changed, 105 insertions(+), 62 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java index bbce7b09e9b67..9e11451afa064 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java @@ -71,6 +71,7 @@ public class CropView extends View { private int mImageWidth; private CropBoundary mCurrentDraggingBoundary = CropBoundary.NONE; + private int mActivePointerId; // The starting value of mCurrentDraggingBoundary's crop, used to compute touch deltas. private float mMovementStartValue; private float mStartingY; // y coordinate of ACTION_DOWN @@ -138,35 +139,60 @@ public class CropView extends View { public boolean onTouchEvent(MotionEvent event) { int topPx = fractionToVerticalPixels(mCrop.top); int bottomPx = fractionToVerticalPixels(mCrop.bottom); - switch (event.getAction()) { + switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: mCurrentDraggingBoundary = nearestBoundary(event, topPx, bottomPx, fractionToHorizontalPixels(mCrop.left), fractionToHorizontalPixels(mCrop.right)); if (mCurrentDraggingBoundary != CropBoundary.NONE) { + mActivePointerId = event.getPointerId(0); mStartingY = event.getY(); mStartingX = event.getX(); mMovementStartValue = getBoundaryPosition(mCurrentDraggingBoundary); - updateListener(event); + updateListener(MotionEvent.ACTION_DOWN, event.getX()); mMotionRange = getAllowedValues(mCurrentDraggingBoundary); } return true; case MotionEvent.ACTION_MOVE: if (mCurrentDraggingBoundary != CropBoundary.NONE) { - float deltaPx = isVertical(mCurrentDraggingBoundary) ? event.getY() - mStartingY - : event.getX() - mStartingX; - float delta = pixelDistanceToFraction((int) deltaPx, mCurrentDraggingBoundary); - setBoundaryPosition(mCurrentDraggingBoundary, - mMotionRange.clamp(mMovementStartValue + delta)); - updateListener(event); - invalidate(); + int pointerIndex = event.findPointerIndex(mActivePointerId); + if (pointerIndex >= 0) { + // Original pointer still active, do the move. + float deltaPx = isVertical(mCurrentDraggingBoundary) + ? event.getY(pointerIndex) - mStartingY + : event.getX(pointerIndex) - mStartingX; + float delta = pixelDistanceToFraction((int) deltaPx, + mCurrentDraggingBoundary); + setBoundaryPosition(mCurrentDraggingBoundary, + mMotionRange.clamp(mMovementStartValue + delta)); + updateListener(MotionEvent.ACTION_MOVE, event.getX(pointerIndex)); + invalidate(); + } return true; } + break; + case MotionEvent.ACTION_POINTER_DOWN: + if (mActivePointerId == event.getPointerId(event.getActionIndex()) + && mCurrentDraggingBoundary != CropBoundary.NONE) { + updateListener(MotionEvent.ACTION_DOWN, event.getX(event.getActionIndex())); + return true; + } + break; + case MotionEvent.ACTION_POINTER_UP: + if (mActivePointerId == event.getPointerId(event.getActionIndex()) + && mCurrentDraggingBoundary != CropBoundary.NONE) { + updateListener(MotionEvent.ACTION_UP, event.getX(event.getActionIndex())); + return true; + } + break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: - if (mCurrentDraggingBoundary != CropBoundary.NONE) { - updateListener(event); + if (mCurrentDraggingBoundary != CropBoundary.NONE + && mActivePointerId == event.getPointerId(mActivePointerId)) { + updateListener(MotionEvent.ACTION_UP, event.getX(0)); + return true; } + break; } return super.onTouchEvent(event); } @@ -308,12 +334,29 @@ public class CropView extends View { return null; } - private void updateListener(MotionEvent event) { - if (mCropInteractionListener != null && (isVertical(mCurrentDraggingBoundary))) { + /** + * @param action either ACTION_DOWN, ACTION_UP or ACTION_MOVE. + * @param x coordinate of the relevant pointer. + */ + private void updateListener(int action, float x) { + if (mCropInteractionListener != null && isVertical(mCurrentDraggingBoundary)) { float boundaryPosition = getBoundaryPosition(mCurrentDraggingBoundary); - mCropInteractionListener.onCropMotionEvent(event, mCurrentDraggingBoundary, - boundaryPosition, fractionToVerticalPixels(boundaryPosition), - (mCrop.left + mCrop.right) / 2); + switch (action) { + case MotionEvent.ACTION_DOWN: + mCropInteractionListener.onCropDragStarted(mCurrentDraggingBoundary, + boundaryPosition, fractionToVerticalPixels(boundaryPosition), + (mCrop.left + mCrop.right) / 2, x); + break; + case MotionEvent.ACTION_MOVE: + mCropInteractionListener.onCropDragMoved(mCurrentDraggingBoundary, + boundaryPosition, fractionToVerticalPixels(boundaryPosition), + (mCrop.left + mCrop.right) / 2, x); + break; + case MotionEvent.ACTION_UP: + mCropInteractionListener.onCropDragComplete(); + break; + + } } } @@ -545,12 +588,11 @@ public class CropView extends View { * Listen for crop motion events and state. */ public interface CropInteractionListener { - /** - * Called whenever CropView has a MotionEvent that can impact the position of the crop - * boundaries. - */ - void onCropMotionEvent(MotionEvent event, CropBoundary boundary, float boundaryPosition, - int boundaryPositionPx, float horizontalCenter); + void onCropDragStarted(CropBoundary boundary, float boundaryPosition, + int boundaryPositionPx, float horizontalCenter, float x); + void onCropDragMoved(CropBoundary boundary, float boundaryPosition, + int boundaryPositionPx, float horizontalCenter, float x); + void onCropDragComplete(); } static class SavedState extends BaseSavedState { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java index 08cd91ccada54..34b40f79836b8 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java @@ -28,7 +28,6 @@ import android.graphics.Path; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.AttributeSet; -import android.view.MotionEvent; import android.view.View; import android.view.ViewPropertyAnimator; @@ -148,49 +147,51 @@ public class MagnifierView extends View implements CropView.CropInteractionListe } @Override - public void onCropMotionEvent(MotionEvent event, CropView.CropBoundary boundary, - float cropPosition, int cropPositionPx, float horizontalCenter) { + public void onCropDragStarted(CropView.CropBoundary boundary, float boundaryPosition, + int boundaryPositionPx, float horizontalCenter, float x) { mCropBoundary = boundary; mLastCenter = horizontalCenter; - boolean touchOnRight = event.getX() > getParentWidth() / 2; + boolean touchOnRight = x > getParentWidth() / 2; float translateXTarget = touchOnRight ? 0 : getParentWidth() - getWidth(); - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - mLastCropPosition = cropPosition; - setTranslationY(cropPositionPx - getHeight() / 2); - setPivotX(getWidth() / 2); - setPivotY(getHeight() / 2); - setScaleX(0.2f); - setScaleY(0.2f); - setAlpha(0f); - setTranslationX((getParentWidth() - getWidth()) / 2); - setVisibility(View.VISIBLE); - mTranslationAnimator = - animate().alpha(1f).translationX(translateXTarget).scaleX(1f).scaleY(1f); - mTranslationAnimator.setListener(mTranslationAnimatorListener); - mTranslationAnimator.start(); - break; - case MotionEvent.ACTION_MOVE: - // The touch is near the middle if it's within 10% of the center point. - // We don't want to animate horizontally if the touch is near the middle. - boolean nearMiddle = Math.abs(event.getX() - getParentWidth() / 2) - < getParentWidth() / 10f; - boolean viewOnLeft = getTranslationX() < (getParentWidth() - getWidth()) / 2; - if (!nearMiddle && viewOnLeft != touchOnRight && mTranslationAnimator == null) { - mTranslationAnimator = animate().translationX(translateXTarget); - mTranslationAnimator.setListener(mTranslationAnimatorListener); - mTranslationAnimator.start(); - } - mLastCropPosition = cropPosition; - setTranslationY(cropPositionPx - getHeight() / 2); - invalidate(); - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - animate().alpha(0).translationX((getParentWidth() - getWidth()) / 2).scaleX(0.2f) - .scaleY(0.2f).withEndAction(() -> setVisibility(View.INVISIBLE)).start(); - break; + mLastCropPosition = boundaryPosition; + setTranslationY(boundaryPositionPx - getHeight() / 2); + setPivotX(getWidth() / 2); + setPivotY(getHeight() / 2); + setScaleX(0.2f); + setScaleY(0.2f); + setAlpha(0f); + setTranslationX((getParentWidth() - getWidth()) / 2); + setVisibility(View.VISIBLE); + mTranslationAnimator = + animate().alpha(1f).translationX(translateXTarget).scaleX(1f).scaleY(1f); + mTranslationAnimator.setListener(mTranslationAnimatorListener); + mTranslationAnimator.start(); + } + + @Override + public void onCropDragMoved(CropView.CropBoundary boundary, float boundaryPosition, + int boundaryPositionPx, float horizontalCenter, float x) { + boolean touchOnRight = x > getParentWidth() / 2; + float translateXTarget = touchOnRight ? 0 : getParentWidth() - getWidth(); + // The touch is near the middle if it's within 10% of the center point. + // We don't want to animate horizontally if the touch is near the middle. + boolean nearMiddle = Math.abs(x - getParentWidth() / 2) + < getParentWidth() / 10f; + boolean viewOnLeft = getTranslationX() < (getParentWidth() - getWidth()) / 2; + if (!nearMiddle && viewOnLeft != touchOnRight && mTranslationAnimator == null) { + mTranslationAnimator = animate().translationX(translateXTarget); + mTranslationAnimator.setListener(mTranslationAnimatorListener); + mTranslationAnimator.start(); } + mLastCropPosition = boundaryPosition; + setTranslationY(boundaryPositionPx - getHeight() / 2); + invalidate(); + } + + @Override + public void onCropDragComplete() { + animate().alpha(0).translationX((getParentWidth() - getWidth()) / 2).scaleX(0.2f) + .scaleY(0.2f).withEndAction(() -> setVisibility(View.INVISIBLE)).start(); } private Path generateCheckerboard() {