Stylus palm rejection

Five star rating icon

A stylus can be an exceptionally productive and creative tool. But when users draw, write, or interact with an app using a stylus, they sometimes touch the screen with the palm of their hands. The touch event can be reported to your app before the system recognizes and dismisses the event as an accidental palm touch.

Best practices

Your app must identify extraneous touch events and ignore them. In Jetpack Compose, you can access the underlying Android MotionEvent from a PointerEvent. Check the MotionEvent for ACTION_CANCEL, which indicates that a gesture should stop and may need to be rolled back. On Android 13 (API level 33) and higher, check for the FLAG_CANCELED flag on ACTION_CANCEL and ACTION_POINTER_UP events, which provides a strong signal that the touch was unintentional, such as a palm touch.

Ingredients

  • Modifier.pointerInput(): The Compose modifier used to process pointer input.
  • PointerEvent: Represents a pointer event in Compose, which contains one or more changes.
  • PointerEvent.motionEvent: Extension property that provides access to the underlying Android MotionEvent (nullable).
  • MotionEvent: Represents touch and movement events. Contains the information necessary to determine whether an event should be disregarded.
  • ACTION_CANCEL: MotionEvent constant that indicates a gesture has been canceled; the gesture should stop and may need to be rolled back.
  • ACTION_POINTER_UP: MotionEvent constant that indicates a pointer other than the first pointer has gone up (that is, has relinquished contact with the device screen).
  • FLAG_CANCELED: MotionEvent constant that indicates that the pointer going up caused an unintentional touch event. Added to ACTION_POINTER_UP and ACTION_CANCEL events on Android 13 (API level 33) and higher.

Steps

Examine MotionEvent objects dispatched to your app. Use the MotionEvent APIs to determine event characteristics:

  • Single-pointer events — Check for ACTION_CANCEL, which indicates the gesture should stop. On Android 13 and higher, also check for FLAG_CANCELED to confirm the touch was unintentional.
  • Multi-pointer events — On Android 13 and higher, check for ACTION_POINTER_UP and FLAG_CANCELED to identify unintentional touches.

Respond to these events by stopping the gesture and rolling back any temporary changes.

1. Acquire motion event objects

Use Modifier.pointerInput to process pointer input on a Composable. Within the pointer input scope, use awaitPointerEventScope and awaitPointerEvent to receive events, and retrieve the underlying Android MotionEvent using the motionEvent property:

import androidx.compose.ui.input.pointer.pointerInput
    Box(
        modifier = Modifier
            .fillMaxSize()
            .pointerInput(Unit) {
                awaitPointerEventScope {
                    while (true) {
                        val event = awaitPointerEvent()
                        val motionEvent = event.motionEvent
                        if (motionEvent != null) {
                            // Process motion event.
                        }
                    }
                }
            }
    )

2. Determine the event action and flags

Check the MotionEvent for ACTION_CANCEL, which indicates a single-pointer event on all API levels. On Android 13 and higher, check ACTION_POINTER_UP for FLAG_CANCELED:

import androidx.compose.ui.input.pointer.pointerInput
    Box(
        modifier = Modifier
            .fillMaxSize()
            .pointerInput(Unit) {
                awaitPointerEventScope {
                    while (true) {
                        val event = awaitPointerEvent()
                        val motionEvent = event.motionEvent ?: continue

                        when (motionEvent.actionMasked) {
                            MotionEvent.ACTION_CANCEL -> {
                                // Process canceled single-pointer motion event for all SDK versions.
                            }
                            MotionEvent.ACTION_POINTER_UP -> {
                                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
                                    (motionEvent.flags and MotionEvent.FLAG_CANCELED) == MotionEvent.FLAG_CANCELED
                                ) {
                                    // Process canceled multi-pointer motion event for Android 13 and higher.
                                }
                            }
                        }
                    }
                }
            }
    )

3. Undo the gesture

Once you've identified a palm touch, you can undo the onscreen effects of the gesture.

Your app must keep a history of user actions so that unintended inputs such as palm touches can be undone. See Implement a basic drawing app in the Enhance stylus support in an Android app codelab for an example.

Results

Your app can now identify and reject palm touches for multi-pointer events on Android 13 and higher API levels and for single-pointer events on all API levels.

Additional resources

For more information, see the following: