Pressure, hover and stylus input¶
AutoMobile injects finger touches. It cannot yet drive stylus-only drawing modes, pressure-sensitive brushes, palm rejection, hover previews, Apple Pencil interactions or force-press menus. This note records what each platform’s injection path can carry and the direction that follows (#12539). Companion spike: Stylus, pressure and hover injection.
Status: shipped so far (#13081): a stylus pointer on tapAt and a straight
swipeAt for Android 12+, at pressure 1.0. Otherwise a research note. Nothing was
verified on physical hardware; the iOS simulator probe is recorded below. Remaining
claims needing a device are marked unverified and carried into the
verification plan.
Evidence levels¶
| Label | Meaning |
|---|---|
| Source | Read from the platform source or header quoted below. |
| Measured | Run against the Playground Pointer Probe on an emulator; the issue named holds the log. |
| Reported | Described by a third party (forum, tracker); not reproduced here. |
| Unverified | Plausible from the above, but needs a run on a device or emulator before relying on it. |
Android¶
What AutoMobile can carry today: fingers only¶
Android touch input reaches the device two ways, and both inject a finger:
- CtrlProxy’s
AccessibilityService.dispatchGesture(CtrlProxy.kt:performTapCoordinates,performSwipe,performDrag,performPinch, and the streamingStrokeDescriptioncoordinator). adb shell input swipe, with no source prefix so the shell defaults totouchscreen.ExecuteGesture.tsuses it as its default swipe mode (the accessibility swipe is the opt-ina11ymode), andandroidLongPress.tsexpresses a long press as a zero-distance swipe with a duration.
Source: GestureDescription carries no input-device data. A stroke is a Path
with a start time and duration. The internal TouchPoint has only a stroke id,
continuation id, start/end-of-path flags and x/y. There is no field for tool
type, pressure, tilt, button state or hover, so no change inside CtrlProxy’s
accessibility dispatch can produce a stylus event. Beyond geometry, timing and stroke
continuation, the only per-gesture knob is the target display
(Builder.setDisplayId, API 30).
adb shell input: stylus tool type, fixed pressure, no hover¶
Source: InputShellCommand (AOSP main,
services/core/java/com/android/server/input/InputShellCommand.java):
- The shell sources include
stylus,mouse,touchscreen,touchpadand others. The tool type is derived from the source:stylusgivesTOOL_TYPE_STYLUS(as does the Bluetooth stylus source, which the shell does not name);mousegivesTOOL_TYPE_MOUSE; touch sources giveTOOL_TYPE_FINGER. tap,swipe,draganddropandmotionevent <DOWN|UP|MOVE|CANCEL> x yaccept a source prefix (input stylus swipe ...) and a-d DISPLAY_ID.- The injected axes are
X,YandPRESSUREonly. Pressure is a constant:1.0forDOWNandMOVE,0.0forUP. There is no way to pass tilt, orientation, distance, a button state (stylus barrel button) or an eraser tool type. The--axisoption exists forscrollonly. motioneventtakes noHOVER_*action, so hover cannot be injected this way.
It runs as the shell user, so it needs neither root nor an installed helper. The tool type
is only set from the source from Android 12 (API 31): Input.java from android-7.0.0_r36
through android-11.0.0_r48 builds the event with the legacy MotionEvent.obtain overload,
which carries no tool type, while android-12.0.0_r1 assigns it (#13081). Measured
(#13082): on API 30 input stylus arrives as source STYLUS with tool type UNKNOWN; on API 31,
32, 33 and 37 it arrives as TOOL_TYPE_STYLUS. motionevent accepts only DOWN, UP, MOVE
and, from API 31, CANCEL; HOVER_MOVE is rejected as Unknown action. Not checked:
releases before API 24.
So adb shell input can produce a binary stylus (stylus tool type, full pressure
for the whole stroke). It cannot produce a pressure curve, tilt, an eraser or hover.
Routes to richer input¶
Measured on an API 37 google_apis userdebug emulator against the Pointer Probe (#13082) unless a
cell says Source:
| Route | Tool type | Pressure curve | Tilt | Buttons | Eraser | Hover | Needs |
|---|---|---|---|---|---|---|---|
Accessibility dispatchGesture (today) |
Finger (Source) | No (Source) | No | No | No | No (Source) | Accessibility service |
adb shell input stylus tap/swipe/motionevent |
Stylus (API 31+) | No, fixed 1.0 | No | No | No | No | adb only |
adb shell input mouse tap |
Mouse | No, fixed 1.0 | No | No | No | One HOVER_ENTER before DOWN only |
adb only |
UiAutomation.injectInputEvent (instrumentation) |
Stylus or mouse | Yes, any 0-1 | Yes | STYLUS_PRIMARY |
No (the route rejects it) | Yes, enter/move/exit, stylus or mouse | am instrument --no-restart: API 31+, no root |
Virtual stylus through uinput (kernel evdev) |
Stylus and eraser | Yes, scaled from the axis | Yes | BTN_STYLUS as STYLUS_PRIMARY |
Yes (BTN_TOOL_RUBBER) |
Yes (BTN_TOOL_PEN without BTN_TOUCH) |
adb root (userdebug or emulator); no idc or keylayout |
Emulator gRPC streamInputEvent with a pen_event |
Stylus | Yes, scaled from 0-1024 | No | button_pressed as STYLUS_PRIMARY |
No (rubber_pointer arrives as a finger) |
No (pressure 0 delivers nothing) | Emulator started with -grpc <port>; no root, no auth |
Emulator console event send raw evdev |
Unverified | Unverified | Unverified | Unverified | Unverified | Unverified | Emulator only |
Instrumentation.sendPointerSync and similar |
n/a | n/a | n/a | n/a | n/a | n/a | Own process only |
So the instrumentation route is the only non-root route with tilt and hover, and uinput is the only
route with an eraser. The emulator has no sendPen RPC: pen input goes through
EmulatorController/streamInputEvent with InputEvent.pen_event
(emulator/lib/emulator_controller.proto). In-process Instrumentation injectors reach only the
instrumented app’s own windows, which does not fit the black-box model.
Every emulator virtio_input_multi_touch_N device reports Sources: KEYBOARD | TOUCHSCREEN |
STYLUS with pressure 0-1024 and no BTN_TOOL_PEN; tilt and distance have no range. So an AVD
advertises a stylus-capable InputDevice but no tilt or distance axes (Measured, API 37).
What the recorder sees¶
recordDeviceGestures reads kernel events with getevent. Since #13101 the recorder
also reads pen data (Source):
- Pen nodes.
InputNodeDiscovery.tsfinds the touch node and any separate single-touch pen node (ABS_X/ABS_YplusBTN_TOOL_PENorBTN_TOOL_RUBBER), andGetEventReaderruns onegetevent -ltprocess per node. - Pen state.
TouchFrameReconstructor.tstracksABS_MT_TOOL_TYPE,BTN_TOOL_PEN/BTN_TOOL_RUBBER,BTN_TOUCH,BTN_STYLUS/BTN_STYLUS2,ABS_TILT_X/ABS_TILT_YandABS_DISTANCE/ABS_MT_DISTANCE. A pen in range without contact is hover and never becomes a tap or swipe. A pen that comes into range and leaves again without ever touching (a single-touch pen’s tool key down then up, or a multitouch pen reporting a hover distance until its tracking id ends) is reported once as ahoverevent, which the recorder turns into a stop-result warning. Hover around a contact is not reported, and a digitizer that reports no hover distance cannot be told from contact. - Pressure axes. A touch node reports
ABS_MT_PRESSURE; a single-touch pen node reports the non-multitouchABS_PRESSUREinstead. Discovery reads each node’s axis range fromgetevent -p, and the raw values are scaled to 0-1 against it; without a usable range the raw values are kept andpressure.normalizedis false. - Gesture fields.
GestureEventcarries optionaltoolType(stylus/eraser),pressure(min, max and mean, normalized when the axis range is known) andbuttons. A pen gesture exports as a coordinate step carryingpointer: { toolType: "stylus" }(or"eraser"): a tap or long press astapAt, a swipe asswipeAt, nevertapOnorswipeOn, which cannot say “stylus”. Recorded pressure is not exported, because replay injects the fixed 1.0 and refuses anything else. A pen gesture with no known position or path is left out with a warning on the stop result, which a salvaged stop keeps. An eraser gesture is exported with a warning because only a rootadb shellwith theuinputtool can replay it. A stylus double tap is exported with a warning because replay refuses it. A stylus tap and a finger tap never merge into one double tap. Finger gestures export unchanged. Hover is not exported (it only warns), nor are stroke paths or pressure series.
One structural limit remains, and extending the axes alone does not remove it (Source):
- No drag event.
GestureEventTypehas no drag, andDualTrackRecordermaps a moving single-contact gesture toswipeAtorswipeOn, which keep only the endpoints. A drawn stroke loses its path.
iOS¶
What the runner can carry today: touches only¶
Source: CtrlProxy already drives private XCPointerEventPath /
XCSynthesizedEventRecord for pinch and multi-finger swipe
(ObjCExceptionCatcher.m), guarded by selector probes so a missing symbol is
reported as an availability gap, not a crash. Its own declaration of
XCPointerEventPath (ObjCExceptionCatcher.h) exposes only touch initialization,
moveToPoint:atOffset: and liftUpAtOffset:. The pressure and mouse selectors below
come from the header of the WebDriverAgent fork of those private classes
(PrivateHeaders/XCTest/XCPointerEventPath.h); CtrlProxy neither declares nor calls
them, so they are only a potential private API until the runner adds and exercises
them. That header declares:
- touch paths:
initForTouchAtPoint:offset:,moveToPoint:atOffset:,liftUpAtOffset:; - pressure:
pressDownWithPressure:atOffset:andpressDownWithPressure:stage:phase:atOffset:; - mouse/pointer paths:
initForMouseEvents*,moveMouseToPoint:atOffset:duration:,pressButton:,releaseButton:,dragWithButton:, plusscrollWithDeltaVector:; deviceIDandpathTypeproperties.
It declares nothing for Apple Pencil tilt, azimuth, roll or hover, and no stylus tool type.
The simulator probe in #13083 establishes two limits:
- Pressure. The private pressure selectors accept and range-check values, and
raw
XCPointerEventpressure is delivered, butUITouch.forcestays0.000. Pressure and Pencil injection are a no-go on the simulator: pressure has no observable effect, and selector inventory exposes no pen, Pencil, tilt, azimuth, altitude or tool-type selector onXCPointerEvent,XCPointerEventPathorXCSynthesizedEventRecord.setDeviceIDvalues 0–3 still produce touch type 0. Public force press returns “Force press events are not supported for this device.” A pressure fallback cannot be validated there. Reported: Appium pressure actions likewise succeed without visible effect (Appium forum thread). - Hover. Mouse pointer paths produce
UIHoverGestureRecognizerbegin/end callbacks on the simulator. Pencil hover is not observed. PublicXCUIElement.hover()andXCUICoordinate.hover()exist, but this observation is from mouse pointer paths and does not establish the public calls’ behavior.
Physical devices¶
The runner on a physical device uses the same XCTest event synthesis. There is no
public XCTest API for Pencil input, so a real Pencil’s pressure, tilt and hover look
impossible without the accessory and, with the private paths, unverified.
recordDeviceGestures is Android-only (deviceGestureRecordingManager.ts rejects other
platforms), so there is no iOS recorder representation to extend.
Capability matrix¶
| Capability | Android emulator | Android device | iOS simulator | iOS device |
|---|---|---|---|---|
| Stylus tool type, constant pressure | Supported via adb shell input stylus on API 31+; API 30 reports tool type UNKNOWN (Measured) |
Same (Source) | No-go: no Pencil tool type; accepted pressure leaves force 0 (#13083) | Unverified |
| Pressure curve | Instrumentation, uinput (root) and gRPC pen (Measured) |
Unverified (UiAutomation; uinput is root-only) |
No-go: accepted pressure leaves force 0 (#13083) | Unverified |
| Tilt, eraser, barrel button | Tilt and button via instrumentation; eraser via uinput only (Measured) |
Unverified (UiAutomation; uinput is root-only) |
No-go via known paths: no pen or tool-type selectors (#13083) | Impossible without a Pencil |
| Hover | Instrumentation (stylus or mouse) and uinput (Measured) |
Unverified (UiAutomation; uinput is root-only) |
Observed on mouse pointer paths; Pencil hover not observed (#13083) | Unverified |
| Records the above | Partly: tool type, pressure, buttons (#13101); not hover or replay (Source) | Same (Source) | No (Source, Android-only recorder) | No (Source) |
“Supported” means a documented path exists in source. Mouse pointer hover is observed on the iOS simulator; pressure and Pencil injection are a no-go there.
Direction¶
Only the direction is fixed here; the design lives in the follow-up issues, where it is specified and reviewed when the work is picked up.
- Pointer options on the coordinate tools. Stylus input is an optional option on
tapAt,swipeAtanddragPath, not on the element tools (tapOn,swipeOn): a stylus has no accessibility-element meaning. - Fail loudly instead of downgrading. A device or path that cannot honor a requested pointer returns an actionable error. It never falls back to a finger touch: a silent downgrade is the failure reported with Appium pressure above, and it makes a stylus-only UI test pass for the wrong reason.
- Hover is its own tool. A
hoverAttool has different semantics from a gesture (no contact, no scroll or fling) and the weakest platform support, so it is not part of the first slice. - Stage by what is proven. The only path demonstrated in source is the stylus
source of
adb shell input, at a fixed pressure of1.0. The first slice is that, fortapAtand a straightswipeAt; pressure curves, tilt, eraser, hover anddragPathwait for the experiments below. - Recording follows the tools. A stylus step is never silently exported or replayed as a finger step; hover, stroke path and pressure series are the recorder gaps under What the recorder sees.
Verification plan¶
Open items are answered by running them, each as a follow-up issue (below):
- Android: a physical pen device’s
dumpsys inputaxes, emulator-consoleevent send, how a panel key or role maps onto-d DISPLAY_ID, and whether two shell invocations hold a double-tap interval. - iOS simulator: the probe in #13083 (Xcode 26.6, iPad Pro 13-inch (M5),
iOS 26.5, base
f8d061423c9) establishes pressure and Pencil injection as a no-go: accepted pressure leavesUITouch.forceat 0, and no Pencil tool type is exposed. Mouse pointer hover producesUIHoverGestureRecognizerbegin/end; Pencil hover is not observed. - Physical iOS device: repeat the probe if a device is available; otherwise record Pencil as out of scope.
Follow-up issues¶
-
13081: stylus tool type at fixed pressure on
throughtapAtand a straightswipeAt¶adb shell input, with capability-gated errors. -
13082: Android emulator and device experiments (pressure curve, tilt, hover, eraser¶
and barrel buttons,UiAutomation.injectInputEvent,uinput, minimum API level). -
13083: iOS simulator and device experiments (pressure, hover through the public¶
hover()calls first, private selectors as the fallback). -
13084: recorder capture of tool type, pressure, hover and the pen’s event node, and¶
export of the pointer.