Zygote
Input & windowsintermediate

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.

13 minAPI 1–37 · 17interactivequizverified 2026-10-27

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 a MotionEvent by InputReader inside system_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 dispatchTouchEvent down 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#

Loading diagram…
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:

  1. Finds the focused window for key events, or hit-tests the touchable region of windows for touch events.
  2. Checks that the window is visible and touchable at that point.
  3. 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 1–API 7Basic touchsingle pointer, no gesture vocabularyAPI 1
API 8 →Multi-touchpointer IDs and gesture detectionAPI 8
API 28 →Gesture navigationthe system claims edge swipesAPI 28
API 33 →Predictive backback becomes an interactive gestureAPI 33
Interface changes16 releases
APIVersionWhat changed
11.0
  • Pull-down notification shade
  • Home screen with widgets
31.5 Cupcake
  • Home screen widgets and folders
92.3 Gingerbread
  • New UI with dark theme and green accents
113.0 Honeycomb
  • The Holo design language arrives
123.1 Honeycomb
  • Resizable widgets
164.1 Jelly Bean
  • Project Butter: vsync-driven, smooth UI
  • Expandable notifications
204.4W KitKat (Wear)
  • Voice-driven card UI
215.0 Lollipop
  • Material Design
  • Notifications on the lock screen
247.0 Nougat
  • Quick Settings tiles
  • Direct Reply in notifications
268.0 Oreo
  • Notification channels
289 Pie
  • Dark theme groundwork
2910
  • System-wide dark theme
3011
  • Conversation notifications and bubbles
  • Media controls in Quick Settings
3112
  • Material You dynamic colour
3313 Tiramisu
  • Themed app icons
  • Runtime notification permission
3616 Baklava
  • Material 3 Expressive
  • Live Updates notifications

Under the hood#

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_MOVE events in one read, which is why MotionEvent.getHistoricalX exists — 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#

Raw touch events
adb shell getevent -lt /dev/input/event3
Example output
/dev/input/event3: 0003 0039 00000001
/dev/input/event3: 0003 0035 000002a4
/dev/input/event3: 0003 0036 000005c8
/dev/input/event3: 0000 0000 00000000
Which window has focus
adb shell dumpsys input | grep -i "focusedwindow|focusedApplication"
Example output
mFocusedWindow=Window{9c1f3a u0 com.example.app/com.example.app.MainActivity}
Window input state
adb shell dumpsys window | grep -i "inputchannel|touchable"
Example output
mInputChannel=android.view.InputChannel@...
mTouchableRegion=...

Check yourself#

Check yourself

1 / 12

Why does the system deliver input to an app through a socket pair rather than a Binder call?

0 answered · 0 correct

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.

  1. docsInput overviewsource.android.comaccessed 2026-10-27↩
  2. docsAnrdeveloper.android.comaccessed 2026-10-27↩
  3. docsHandle input eventsdeveloper.android.comaccessed 2026-10-27↩
  4. docsBehavior changes: Apps targeting Android 12developer.android.comaccessed 2026-10-27↩
  5. docsWinscopesource.android.comaccessed 2026-10-27↩
  6. docsAndroid tracing with Perfettoperfetto.devaccessed 2026-10-27↩