55import android .view .MotionEvent ;
66import android .view .VelocityTracker ;
77import android .view .ViewConfiguration ;
8+ import io .sentry .ISentryLifecycleToken ;
9+ import io .sentry .util .AutoClosableReentrantLock ;
810import org .jetbrains .annotations .ApiStatus ;
911import org .jetbrains .annotations .NotNull ;
1012import org .jetbrains .annotations .Nullable ;
@@ -35,6 +37,8 @@ public final class SentryGestureDetector {
3537 private @ Nullable MotionEvent currentDownEvent ;
3638 private @ Nullable VelocityTracker velocityTracker ;
3739
40+ private final @ NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock ();
41+
3842 SentryGestureDetector (
3943 final @ NotNull Context context , final @ NotNull GestureDetector .OnGestureListener listener ) {
4044 this .listener = listener ;
@@ -46,102 +50,101 @@ public final class SentryGestureDetector {
4650 }
4751
4852 void onTouchEvent (final @ NotNull MotionEvent event ) {
49- final int action = event .getActionMasked ();
53+ try (final @ NotNull ISentryLifecycleToken ignored = lock .acquire ()) {
54+ final int action = event .getActionMasked ();
55+
56+ if (velocityTracker == null ) {
57+ velocityTracker = VelocityTracker .obtain ();
58+ }
59+ velocityTracker .addMovement (event );
60+
61+ switch (action ) {
62+ case MotionEvent .ACTION_DOWN :
63+ downX = event .getX ();
64+ downY = event .getY ();
65+ lastX = downX ;
66+ lastY = downY ;
67+ isInTapRegion = true ;
68+ ignoreUpEvent = false ;
69+
70+ if (currentDownEvent != null ) {
71+ currentDownEvent .recycle ();
72+ }
73+ currentDownEvent = MotionEvent .obtain (event );
5074
51- if (velocityTracker == null ) {
52- velocityTracker = VelocityTracker .obtain ();
53- }
75+ listener .onDown (event );
76+ break ;
5477
55- if (action == MotionEvent .ACTION_DOWN ) {
56- velocityTracker .clear ();
57- }
58- velocityTracker .addMovement (event );
59-
60- switch (action ) {
61- case MotionEvent .ACTION_DOWN :
62- downX = event .getX ();
63- downY = event .getY ();
64- lastX = downX ;
65- lastY = downY ;
66- isInTapRegion = true ;
67- ignoreUpEvent = false ;
68-
69- if (currentDownEvent != null ) {
70- currentDownEvent .recycle ();
71- }
72- currentDownEvent = MotionEvent .obtain (event );
73-
74- listener .onDown (event );
75- break ;
76-
77- case MotionEvent .ACTION_MOVE :
78- {
79- final float x = event .getX ();
80- final float y = event .getY ();
81- final float dx = x - downX ;
82- final float dy = y - downY ;
83- final float distanceSquare = (dx * dx ) + (dy * dy );
84-
85- if (distanceSquare > touchSlopSquare ) {
86- final float scrollX = lastX - x ;
87- final float scrollY = lastY - y ;
88- listener .onScroll (currentDownEvent , event , scrollX , scrollY );
89- isInTapRegion = false ;
90- lastX = x ;
91- lastY = y ;
78+ case MotionEvent .ACTION_MOVE :
79+ {
80+ final float x = event .getX ();
81+ final float y = event .getY ();
82+ final float dx = x - downX ;
83+ final float dy = y - downY ;
84+ final float distanceSquare = (dx * dx ) + (dy * dy );
85+
86+ if (distanceSquare > touchSlopSquare ) {
87+ final float scrollX = lastX - x ;
88+ final float scrollY = lastY - y ;
89+ listener .onScroll (currentDownEvent , event , scrollX , scrollY );
90+ isInTapRegion = false ;
91+ lastX = x ;
92+ lastY = y ;
93+ }
94+ break ;
9295 }
96+
97+ case MotionEvent .ACTION_POINTER_DOWN :
98+ // A second finger means this is not a single tap (e.g. pinch-to-zoom).
99+ // Also suppress the UP handler to avoid spurious fling detection when the
100+ // last finger lifts quickly after a pinch — mirrors GestureDetector's
101+ // mIgnoreNextUpEvent / cancelTaps() behavior.
102+ isInTapRegion = false ;
103+ ignoreUpEvent = true ;
93104 break ;
94- }
95-
96- case MotionEvent .ACTION_POINTER_DOWN :
97- // A second finger means this is not a single tap (e.g. pinch-to-zoom).
98- // Also suppress the UP handler to avoid spurious fling detection when the
99- // last finger lifts quickly after a pinch — mirrors GestureDetector's
100- // mIgnoreNextUpEvent / cancelTaps() behavior.
101- isInTapRegion = false ;
102- ignoreUpEvent = true ;
103- break ;
104-
105- case MotionEvent .ACTION_UP :
106- if (ignoreUpEvent ) {
107- endGesture ();
108- break ;
109- }
110- if (isInTapRegion ) {
111- listener .onSingleTapUp (event );
112- } else {
113- final int pointerId = event .getPointerId (0 );
114- velocityTracker .computeCurrentVelocity (1000 , maximumFlingVelocity );
115- final float velocityX = velocityTracker .getXVelocity (pointerId );
116- final float velocityY = velocityTracker .getYVelocity (pointerId );
117-
118- if (Math .abs (velocityX ) > minimumFlingVelocity
119- || Math .abs (velocityY ) > minimumFlingVelocity ) {
120- listener .onFling (currentDownEvent , event , velocityX , velocityY );
105+
106+ case MotionEvent .ACTION_UP :
107+ if (ignoreUpEvent ) {
108+ recycle ();
109+ break ;
121110 }
122- }
123- endGesture ();
124- break ;
111+ if (isInTapRegion ) {
112+ listener .onSingleTapUp (event );
113+ } else {
114+ final int pointerId = event .getPointerId (0 );
115+ velocityTracker .computeCurrentVelocity (1000 , maximumFlingVelocity );
116+ final float velocityX = velocityTracker .getXVelocity (pointerId );
117+ final float velocityY = velocityTracker .getYVelocity (pointerId );
118+
119+ if (Math .abs (velocityX ) > minimumFlingVelocity
120+ || Math .abs (velocityY ) > minimumFlingVelocity ) {
121+ listener .onFling (currentDownEvent , event , velocityX , velocityY );
122+ }
123+ }
124+ recycle ();
125+ break ;
125126
126- case MotionEvent .ACTION_CANCEL :
127- endGesture ();
128- break ;
127+ case MotionEvent .ACTION_CANCEL :
128+ recycle ();
129+ break ;
130+ }
129131 }
130132 }
131133
132- /** Releases native resources. Call when the detector is no longer needed. */
133- void release () {
134- endGesture ();
135- if (velocityTracker != null ) {
136- velocityTracker .recycle ();
134+ void recycle () {
135+ final @ Nullable MotionEvent capturedDownEvent ;
136+ final @ Nullable VelocityTracker capturedVelocityTracker ;
137+ try (final @ NotNull ISentryLifecycleToken ignored = lock .acquire ()) {
138+ capturedDownEvent = currentDownEvent ;
139+ currentDownEvent = null ;
140+ capturedVelocityTracker = velocityTracker ;
137141 velocityTracker = null ;
138142 }
139- }
140-
141- private void endGesture () {
142- if (currentDownEvent != null ) {
143- currentDownEvent .recycle ();
144- currentDownEvent = null ;
143+ if (capturedDownEvent != null ) {
144+ capturedDownEvent .recycle ();
145+ }
146+ if (capturedVelocityTracker != null ) {
147+ capturedVelocityTracker .recycle ();
145148 }
146149 }
147150}
0 commit comments