From 5ac477c9d427bdda1b11ae6b72e57aa7cdfefb09 Mon Sep 17 00:00:00 2001 From: Appcelerator Build Date: Mon, 24 May 2021 10:50:39 -0400 Subject: [PATCH] fix(android): longpress event wrongly fires on a tapped view with touch disabled (#12825) Fixes TIMOB-28454 --- .../appcelerator/titanium/view/TiUIView.java | 69 ++++++++++++++----- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/android/titanium/src/java/org/appcelerator/titanium/view/TiUIView.java b/android/titanium/src/java/org/appcelerator/titanium/view/TiUIView.java index 72a803f1cfe..d47a75df2fe 100644 --- a/android/titanium/src/java/org/appcelerator/titanium/view/TiUIView.java +++ b/android/titanium/src/java/org/appcelerator/titanium/view/TiUIView.java @@ -60,8 +60,8 @@ import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; import android.view.View.OnKeyListener; -import android.view.View.OnLongClickListener; import android.view.View.OnTouchListener; +import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; import android.view.ViewTreeObserver.OnGlobalLayoutListener; @@ -132,7 +132,9 @@ public TiBackgroundDrawable getBackground() private float animatedRotationDegrees = 0f; // i.e., no rotation. private float animatedAlpha = Float.MIN_VALUE; // i.e., no animated alpha. - protected KrollDict lastUpEvent = new KrollDict(2); + protected KrollDict lastUpEvent = new KrollDict(3); + private MotionEvent longPressMotionEvent; + // In the case of heavy-weight windows, the "nativeView" is null, // so this holds a reference to the view which is used for touching, // i.e., the view passed to registerForTouch. @@ -1293,6 +1295,7 @@ public void release() if (Log.isDebugModeEnabled()) { Log.d(TAG, "Releasing: " + this, Log.DEBUG_MODE); } + releaseLongPressMotionEvent(); View nv = getNativeView(); if (nv != null) { if (nv instanceof ViewGroup) { @@ -1336,6 +1339,14 @@ public void release() layoutParams = null; } + private void releaseLongPressMotionEvent() + { + if (this.longPressMotionEvent != null) { + this.longPressMotionEvent.recycle(); + this.longPressMotionEvent = null; + } + } + private void setVisibility(int visibility) { if (visibility == View.INVISIBLE) { @@ -1744,23 +1755,21 @@ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float ve } return false; } - - @Override - public void onLongPress(MotionEvent e) - { - Log.d(TAG, "LONGPRESS on " + proxy, Log.DEBUG_MODE); - - if (proxy != null && proxy.hierarchyHasListener(TiC.EVENT_LONGPRESS)) { - fireEvent(TiC.EVENT_LONGPRESS, dictFromEvent(e)); - } - } }); + detector.setIsLongpressEnabled(false); // We do our own "longpress" detection via onTouch(). touchable.setOnTouchListener(new OnTouchListener() { int pointersDown = 0; + int touchSlop; public boolean onTouch(View view, MotionEvent event) { + // Fetch max distance finger can travel until it can't be considered a click/tap. + if (this.touchSlop <= 0) { + this.touchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop(); + } + + // Store position where touch was released for the onClick() listener. if (event.getAction() == MotionEvent.ACTION_UP) { TiDimension xDimension = new TiDimension((double) event.getX(), TiDimension.TYPE_LEFT); TiDimension yDimension = new TiDimension((double) event.getY(), TiDimension.TYPE_TOP); @@ -1769,6 +1778,31 @@ public boolean onTouch(View view, MotionEvent event) lastUpEvent.put(TiC.EVENT_PROPERTY_OBSCURED, wasObscured(event)); } + // Do custom "longpress" event tracking. Store motion event data to be used by onLongClick() listener. + // Note: Can't use GestureDetector for this since we would have to handle onDown() to make it work, + // which would prevent view's onClick() and onLongClick() listeners from being called. + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + // Start tracking. + releaseLongPressMotionEvent(); + longPressMotionEvent = MotionEvent.obtain(event); + break; + case MotionEvent.ACTION_MOVE: + // Stop tracking if dragged too far from initial touch point. + if (longPressMotionEvent != null) { + float deltaX = Math.abs(longPressMotionEvent.getRawX() - event.getRawX()); + float deltaY = Math.abs(longPressMotionEvent.getRawY() - event.getRawY()); + if ((deltaX > this.touchSlop) || (deltaY > this.touchSlop)) { + releaseLongPressMotionEvent(); + } + } + break; + default: + // Stop tracking on ACTION_UP, ACTION_CANCEL, etc. + releaseLongPressMotionEvent(); + break; + } + if (proxy != null && proxy.hierarchyHasListener(TiC.EVENT_PINCH)) { scaleDetector.onTouchEvent(event); if (scaleDetector.isInProgress()) { @@ -2090,11 +2124,14 @@ public boolean fireSyncEvent(String eventName, KrollDict data, boolean bubbles) protected void setOnLongClickListener(View view) { - view.setOnLongClickListener(new OnLongClickListener() { - public boolean onLongClick(View view) - { - return fireEvent(TiC.EVENT_LONGCLICK, null); + view.setOnLongClickListener((View v) -> { + boolean wasHandled = false; + if (this.longPressMotionEvent != null) { + wasHandled = fireEvent(TiC.EVENT_LONGPRESS, dictFromEvent(this.longPressMotionEvent)); + releaseLongPressMotionEvent(); } + wasHandled |= fireEvent(TiC.EVENT_LONGCLICK, null); + return wasHandled; }); }