The input pipeline
From a capacitive touch to a MotionEvent in your app: the kernel driver, InputReader, InputDispatcher, the InputChannel socket, and the timeout that turns a stalled app into an ANR.
Input is the only part of the system where the user is inside the loop. A frame can be late and the app still feels fine; a touch that arrives 200 ms late is immediately wrong, because the finger has moved and the screen has not. That is why the input path is short, mostly native, and has a hard deadline.
TL;DR#
- A touch is a kernel input event, read from
/dev/input/event*, then converted into aMotionEventby InputReader insidesystem_server. - InputDispatcher decides which window gets the event, using the window's position, touchable region and z-order.
- The event crosses into the app through an InputChannel, a socket pair, not through Binder.
- The app's UI thread runs
dispatchTouchEventdown the view tree. - If the app does not finish within the input dispatch timeout, the system raises an input ANR.
Mental model#
Think of a hotel switchboard.
The front desk is the kernel: it takes the call and notes the room.
The switchboard operator — InputReader — translates the raw call into something the hotel understands: "a guest in room 412 wants to speak to reception".
The dispatcher — InputDispatcher — looks up which room currently has the phone and connects the call. It knows which rooms are occupied, which are on the do-not-disturb list, and which room is on top of the pile.
The phone in the room is the InputChannel. The call arrives, and the room's occupant has a limited time to answer before the operator marks the room as unresponsive.
Where the analogy breaks#
A call goes to one room. A touch goes to the first window whose touchable region contains the point, which is not necessarily the topmost window: a window can be visible but not touchable, or touchable only in a defined region. And unlike a phone, the app does not "answer" the event as a whole: it may consume some events in a gesture and not others, and the dispatcher tracks the difference.
The big picture#
Text description of this diagram
A touch followed across seven participants. The touch panel's driver reports raw coordinates through evdev; InputReader applies calibration and display transform to produce a MotionEvent; InputDispatcher hit-tests the window list; the event is written into the window's InputChannel socket; ViewRootImpl's InputEventReceiver reads it and posts it to the UI thread; dispatchTouchEvent walks the view tree; and on ACTION_UP a performClick fires. The final return signals that the app consumed the event, which is what keeps the input dispatch timeout from firing.
The diagram is a sequence, not a stack: each participant hands off to the next, and the last one hands back an acknowledgement. That acknowledgement is the deadline.
Deep dive#
The kernel side#
A capacitive touch panel's driver registers an input device and reports events through evdev: EV_ABS for position,
EV_KEY for the touch state, EV_SYN to mark the end of a report. The events appear as a stream on a device node
under /dev/input/.
Android does not read that stream in every app. A single native component in system_server reads it once and
distributes the result. That is the fundamental design choice: input is a system service, not a per-app concern.
InputReader#
InputReader is a native thread inside system_server. It reads from the input devices, applies the device's
configuration — touch calibration, the display transform for rotation, the pointer ID assignment for multi-touch — and
produces a MotionEvent (or KeyEvent).1
Two details matter more than they look:
- The display transform. A touch is reported in the panel's coordinate space. If the screen is rotated, the same physical point is a different logical point. InputReader applies the transform so the rest of the system never sees panel coordinates.
- The policy. InputReader consults the input policy — the same policy surface that handles gestures, the navigation bar's swipe areas and the power button — before the event reaches an app.
InputDispatcher#
InputDispatcher owns the window list and the focus state.1 For each event it:
- Finds the focused window for key events, or hit-tests the touchable region of windows for touch events.
- Checks that the window is visible and touchable at that point.
- Writes the event into that window's InputChannel.
Hit-testing is top-down by z-order, but "top" is not a single number. A window has a touchable region, which may be
smaller than its frame; the status bar, for example, is visible across the top of the screen but only its icons are
touchable, and the rest passes through to the app below. This is why FLAG_NOT_TOUCHABLE and the touchable region are
such important concepts in window management.
The InputChannel#
An InputChannel is a pair of file descriptors: one end in system_server, one in the app. The app's end is wrapped by
InputEventReceiver, which is integrated with the app's Looper, so a readable event wakes the UI thread's message
queue rather than requiring a dedicated input thread.1
That integration is deliberate. Input is delivered to the same thread that runs the app's UI, because the event is going to touch the view tree, which is not thread-safe. The cost is that a busy UI thread delays input, which is exactly the condition the timeout exists to catch.
The app side#
The event reaches ViewRootImpl, which calls dispatchTouchEvent on the root view. From there the standard view
dispatch rules apply, covered in Touch dispatch in the View tree.
For Compose, the entry point is different — the pointer input system inside the Compose runtime — but the boundary is the same: the event arrives on the UI thread and is dispatched into a tree.
The input ANR#
The dispatcher does not wait forever. If the app does not finish handling an event within the dispatch timeout, the system raises an input ANR and shows the "isn't responding" dialog.2
The distinction that matters: an ANR is not "the app is slow". It is "the app did not acknowledge this specific event within the deadline". A slow but responsive app can produce jank forever without an ANR; a blocked UI thread produces an ANR even if the device is otherwise idle.
Where the timeout lives#
The dispatcher starts a timer when it writes an event to a channel and clears it when the app signals completion. If the timer fires, the dispatcher collects the app's stack traces and asks ActivityManager to show the dialog. This is why the ANR trace shows what the UI thread was doing at that moment, not what it was doing when the event arrived.
What happens to the event between devices#
A phone with a touchscreen, a stylus and a keyboard has three input devices producing three different event streams. InputReader merges them into one stream per app, and the merge has to respect a policy:
- The stylus wins over the finger while the pen is in range, so a palm does not register as a second touch.
- The keyboard's modifier state applies to key events from any device.
- The power button is handled by the input policy before it reaches any app.
That policy layer is why "which device did this come from" is available as InputDevice metadata on the event, but
the app rarely needs it. The system has already resolved the conflict.
Focus, and why key events are different#
Touch events are hit-tested: the point selects the window. Key events are delivered to the focused window, which is a property the system maintains separately.
A window becomes focused when it is the topmost window that is focusable, or when it explicitly requests focus. That
is why a dialog takes the keyboard: it is focusable and it is above the app. It is also why FLAG_NOT_FOCUSABLE
matters for a toast-like window — a focusable overlay would steal every keystroke from the app underneath.
Focus changes are announced through onWindowFocusChanged. An app that starts an animation or polls in that callback
and never stops on focus loss is a classic source of background work that survives the user leaving the screen.
The touchable region, not the frame#
The dispatcher does not test the window's frame; it tests the touchable region, which can be an arbitrary region. Two consequences:
- A window can be visible across the screen and only receive touches in a small area — the status bar is the canonical example.
- A window can extend beyond the display and still have a well-defined touchable region within it.
The region is computed by WindowManager and passed to the dispatcher. An app that draws a floating control outside its window bounds has no touchable region there, which is why such controls do not work unless the window is made larger or a separate window is created.
Multi-touch across windows#
A gesture that starts in one window does not move to another. If a finger goes down inside an app's window and slides outside it, the app keeps receiving the events — the dispatcher has already assigned the gesture to that window for its duration.
This is deliberate: a drag that leaves the window would otherwise be interrupted at the boundary. It also means an
app must handle ACTION_CANCEL when the system takes the gesture away, for example when a system gesture claims it.
Where the system's gestures fit#
The system's own gestures — back, home, recents, the notification shade — are handled by the input policy before the
app sees the event, or by the Shell for the gesture navigation variants. The app is told about the ones that reach it
and about the ones that were taken away through ACTION_CANCEL.
That ordering is why a horizontally-scrolling view near the screen edge can feel unreliable: the system's back gesture and the app's scroll both want the same drag, and the system usually wins.
How it evolved#
| API | Version | What changed |
|---|---|---|
| 1 | 1.0 |
|
| 3 | 1.5 Cupcake |
|
| 9 | 2.3 Gingerbread |
|
| 11 | 3.0 Honeycomb |
|
| 12 | 3.1 Honeycomb |
|
| 16 | 4.1 Jelly Bean |
|
| 20 | 4.4W KitKat (Wear) |
|
| 21 | 5.0 Lollipop |
|
| 24 | 7.0 Nougat |
|
| 26 | 8.0 Oreo |
|
| 28 | 9 Pie |
|
| 29 | 10 |
|
| 30 | 11 |
|
| 31 | 12 |
|
| 33 | 13 Tiramisu |
|
| 36 | 16 Baklava |
|
Under the hood#
- InputReader.cppthe reader thread: devices, calibration, event construction
- InputDispatcher.cpphit-testing, focus, channels and the ANR timer
- ViewRootImpl.javathe app-side receiver and the dispatch entry point
- InputEventReceiver.javahow the channel is integrated with the app's Looper
- InputChannel.javathe socket pair and its file descriptors
Curiosity corner#
- The touch panel is not the only input device. A stylus, a gamepad, a mouse and a keyboard all go through the same pipeline; they differ in the events the reader produces.
- Input can be batched. A slow app may receive several
ACTION_MOVEevents in one read, which is whyMotionEvent.getHistoricalXexists — it gives you the intermediate points the app did not see individually. - The dispatcher knows about ANR before your app does. The system collects the trace, so by the time the dialog appears, the evidence is already recorded.
Try it#
adb shell getevent -lt /dev/input/event3Example output
/dev/input/event3: 0003 0039 00000001
/dev/input/event3: 0003 0035 000002a4
/dev/input/event3: 0003 0036 000005c8
/dev/input/event3: 0000 0000 00000000adb shell dumpsys input | grep -i "focusedwindow|focusedApplication"Example output
mFocusedWindow=Window{9c1f3a u0 com.example.app/com.example.app.MainActivity}adb shell dumpsys window | grep -i "inputchannel|touchable"Example output
mInputChannel=android.view.InputChannel@...
mTouchableRegion=...Check yourself#
Check yourself
Why does the system deliver input to an app through a socket pair rather than a Binder call?
Sources#
Sources
Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.
- docsInput overviewsource.android.comaccessed 2026-10-27↩
- docsAnrdeveloper.android.comaccessed 2026-10-27↩
- docsHandle input eventsdeveloper.android.comaccessed 2026-10-27↩
- docsBehavior changes: Apps targeting Android 12developer.android.comaccessed 2026-10-27↩
- docsWinscopesource.android.comaccessed 2026-10-27↩
- docsAndroid tracing with Perfettoperfetto.devaccessed 2026-10-27↩