On Android, touch events usually come in ACTION_DOWN
and ACTION_UP
pairs. Sometimes, you receive ACTION_CANCEL
instead of the ACTION_UP
event. Thus, always a pair of events which makes perfect sense.
Now, after 13 years of Android
app development, I found a simple way to end up with an ACTION_DOWN
which never results in the expected ACTION_UP
or ACTION_CANCEL
event. In our game, this bug means the game recognizes an eternal touch down. Not ideal for touch based games...
Steps to reproduce
Use a modern device with system gestures, like Pixel 9 Pro.
- Create a new "Empty Views Activity" in Android Studio. Choose
Java
. - Override
onTouchEvent
as shown below. - Start navigation gesture from the right side.
- When the "navigate" arrow appears, swipe back to the right side. The arrow disappears. Release your finger.
@Override
public boolean onTouchEvent(MotionEvent event) {
String s = MotionEvent.actionToString((event.getAction()));
Log.d("TOUCH", s);
return super.onTouchEvent(event);
}
If you successfully canceled the system gesture, you will only see ACTION_DOWN
(and possibly some ACTION_MOVE
) in the log.
Since Google refuse to recognize issues/401872146 I'm turning to the community. Is this expected behavior? Not sure how to interpret the rather ambiguous MotionEvent
Consistency Guarantees.
Canceling a system gesture (starting with ACTION_DOWN
) should produce a corresponding ACTION_UP
or ACTION_CANCEL
. It does not happen.
On Android, touch events usually come in ACTION_DOWN
and ACTION_UP
pairs. Sometimes, you receive ACTION_CANCEL
instead of the ACTION_UP
event. Thus, always a pair of events which makes perfect sense.
Now, after 13 years of Android
app development, I found a simple way to end up with an ACTION_DOWN
which never results in the expected ACTION_UP
or ACTION_CANCEL
event. In our game, this bug means the game recognizes an eternal touch down. Not ideal for touch based games...
Steps to reproduce
Use a modern device with system gestures, like Pixel 9 Pro.
- Create a new "Empty Views Activity" in Android Studio. Choose
Java
. - Override
onTouchEvent
as shown below. - Start navigation gesture from the right side.
- When the "navigate" arrow appears, swipe back to the right side. The arrow disappears. Release your finger.
@Override
public boolean onTouchEvent(MotionEvent event) {
String s = MotionEvent.actionToString((event.getAction()));
Log.d("TOUCH", s);
return super.onTouchEvent(event);
}
If you successfully canceled the system gesture, you will only see ACTION_DOWN
(and possibly some ACTION_MOVE
) in the log.
Since Google refuse to recognize issues/401872146 I'm turning to the community. Is this expected behavior? Not sure how to interpret the rather ambiguous MotionEvent
Consistency Guarantees.
Canceling a system gesture (starting with ACTION_DOWN
) should produce a corresponding ACTION_UP
or ACTION_CANCEL
. It does not happen.
1 Answer
Reset to default 1I guess the intended solution is to ignore events intersecting the system gesture insets.
For me, not getting an ACTION_CANCEL
was very unexpected as well. This unnecessarily breaks old code, which would otherwise be working perfectly fine, since ACTION_CANCEL
would signal exactly the right thing:
The current gesture has been aborted. You will not receive any more points in it. You should treat this as an up event, but not perform any action that you normally would.
Instead of this straight forward solution, every developer now needs to implement additional logic.
I interpret the consistency guarantees as "there are no guarantees":
Views should always be prepared to handle ACTION_CANCEL and should tolerate anomalous situations such as receiving a new ACTION_DOWN without first having received an ACTION_UP for the prior gesture.
But other parts of the documentation imply that every ACTION_DOWN
ends with ACTION_UP
or ACTION_CANCEL
, e.g.:
Each pointer has a unique id that is assigned when it first goes down (indicated by ACTION_DOWN or ACTION_POINTER_DOWN). A pointer id remains valid until the pointer eventually goes up (indicated by ACTION_UP or ACTION_POINTER_UP) or when the gesture is canceled (indicated by ACTION_CANCEL).