CN

Android Systrace Basics - Input Interpretation

Word count: 3.9kReading time: 24 min
2019/11/04
loading

This is the sixth article in the Systrace series, primarily providing a brief introduction to Input in Systrace. It covers the Input workflow, how Input information is represented in Systrace, and how to combine Input info to analyze related performance issues.

The purpose of this series is to view the overall operation of the Android system from a different perspective using Systrace, while also providing an alternative angle for learning the Framework. Perhaps you’ve read many articles about the Framework but can never remember the code, or you’re unclear about the execution flow. Maybe from Systrace’s graphical perspective, you can gain a deeper understanding.

Version note — reviewed 2026-10-13: The 2019 screenshots and original dumpsys data are retained, with AOSP android-9.0.0_r1 as the historical reference and Android 16/Perfetto updates. Queue/trace names are not stable interfaces across every system. Both public attachment traces were inspected as text; no device experiment was rerun.

Table of Contents

Series Article Index

  1. Introduction to Systrace
  2. Systrace Basics - Prerequisites for Systrace
  3. Systrace Basics - Why 60 fps?
  4. Android Systrace Basics - SystemServer Explained
  5. Systrace Basics - SurfaceFlinger Explained
  6. Systrace Basics - Input Explained
  7. Systrace Basics - Vsync Explained
  8. Systrace Basics - Vsync-App: Detailed Explanation of Choreographer-Based Rendering Mechanism
  9. Systrace Basics - MainThread and RenderThread Explained
  10. Systrace Basics - Binder and Lock Contention Explained
  11. Systrace Basics - Triple Buffer Explained
  12. Systrace Basics - CPU Info Explained
  13. Systrace Smoothness in Action 1: Understanding Jank Principles
  14. Systrace Smoothness in Action 2: Case Analysis - MIUI Launcher Scroll Jank Analysis
  15. Systrace Smoothness in Action 3: FAQs During Jank Analysis
  16. Systrace Responsiveness in Action 1: Understanding Responsiveness Principles
  17. Systrace Responsiveness in Action 2: Responsiveness Analysis - Using App Startup as an Example
  18. Systrace Responsiveness in Action 3: Extended Knowledge on Responsiveness
  19. Systrace Thread CPU State Analysis Tips - Runnable
  20. Systrace Thread CPU State Analysis Tips - Running
  21. Systrace Thread CPU State Analysis Tips - Sleep and Uninterruptible Sleep

Main Content

Read this with Perfetto 5: Choreographer and Perfetto 7: MainThread/RenderThread. Locate system entry, target selection, InputChannel publication, app receipt/handling, and completion acknowledgment, then correlate any required frame with presentation. Acknowledging input and displaying its result are different boundaries.

The Choreographer article distinguishes Looper message dispatch from native FD callbacks. Readable input channels can enter Java through the native Looper, so input is not always an ordinary Handler Message. This article focuses on touch MotionEvent; KeyEvent has different focus, interception, and fallback paths. Home/Back are not universally ordinary keys delivered to the current app.

A typical touch path is:

  1. The controller samples according to device policy and the driver reports kernel events. Sampling, reporting, and Reader scheduling frequencies need not match one-to-one.
  2. EventHub reads events; InputReader’s device mappers process coordinates/actions and notify the dispatch chain.
  3. InputDispatcher selects connections using event type, display, touch regions, gesture state, and policy. It does not broadcast to all apps registered for input.
  4. The app handles input immediately or in batches as appropriate. UI changes may schedule/coalesce frame work; one input does not guarantee one new frame.

Time runs left to right, but compare synchronized event/slice boundaries. Cross-thread order alone does not prove causality: check channels, sequences, stacks, and scheduling. I hope this graphical view helps you locate code execution in the larger flow.

Input in Systrace

This historical single-pointer Launcher scroll normally starts with DOWN, continues through MOVE, and ends with UP or CANCEL. Multi-pointer gestures also use POINTER_DOWN/POINTER_UP; Down-Move-Up is not every possible stream.

Historical Launcher input overview with Reader, Dispatcher, connection queues and app tracks

InputReader and InputDispatcher are native system_server threads in this capture. Interpret the other annotations separately:

  1. Reader processes EventHub data; touch uses Motion notifications and keys use Key notifications.
  2. Dispatcher selects targets and maintains dispatch state. Names and trace points are version/vendor dependent.
  3. oq: counts outbound entries for a Connection, which may represent a window or monitor. An app can have multiple windows/connections.
  4. wq: tracks successfully published entries awaiting finished-protocol processing, not only events for which business code must return true.
  5. aq:pending counts queued ViewRootImpl input not yet taken for processing, not all in-flight input.
  6. deliverInputEvent tracks app delivery/processing, not the kernel wakeup instant. Inspect sched_wakeup/sched_switch separately.
  7. The old viewer’s Input Response / Rendering Response are interaction-analysis groupings, not direct universal hardware touch-to-display measurements.
  8. Release can continue into fling when gesture/velocity conditions allow; UP does not always cause fling.

DOWN comes first below. MOVE batching, history, and resampling materially affect timing and are not negligible differences.

InputDown Event Workflow in SystemServer

The image shows Reader work, Dispatcher scheduling, and connection-counter changes. Colors/counts guide investigation; select matching events and read timestamps for exact delays.

Historical DOWN-related Reader, Dispatcher and connection-counter changes

InputDown Event Workflow in App

An ordinary DOWN generally does not follow MOVE’s batching delay and can be delivered once its channel is readable. This is not determined by whether the system “has VSync.” A busy main thread, Runnable scheduling delay, or another input-stage wait can still postpone handling.

Launcher enters deliverInputEvent after receiving a historical DOWN

App’s Pending Queue

One aq:pending sample has value 1

“Back to zero means finished” is too strong. Android 9 ViewRootImpl.doProcessInputEvents() decrements the count after dequeuing and before calling deliverInputEvent(). At zero, an event can still be processing, awaiting an asynchronous stage, or not yet acknowledged. A thread blocked before reading the native channel need not first build up this Java queue.

Main Thread Processing Input Events

The stack shows native poll → InputEventReceiver → ViewRootImpl stages → DecorView/Activity/View dispatch. Other event types can traverse different stages; instrumented method duration is not unqualified normal-runtime cost.

ViewRootImpl input stages reach DecorView, Activity and View dispatch

Key Knowledge Points and Processes

The normal path is Reader notification → Dispatcher inbound/pending → target selection and per-connection outbound → successful publication into wait → app handling → finished sequence/handled response over the input channel → Dispatcher completion policy and removal or continuation.

Events and acknowledgments primarily use the InputChannel socket protocol, not a per-touch Binder callback into Java InputManagerService. handled=false can be a valid result, and finished does not mean the UI has redrawn. Broken-connection cleanup can also reduce queues, so read connection state alongside counts.

InputReader

Using AOSP android-9.0.0_r1 as the reference, EventHub watches input-device FDs and device changes; mappers process RawEvents. Its 256-entry buffer stores RawEvents, not 256 gestures, a sampling rate, or a universal constant. Touch follows NotifyMotionArgs/MotionEntry; Key follows NotifyKeyArgs/KeyEntry.

The historical diagram includes key-specific policy such as interceptKeyBeforeQueueing, not a mandatory path for every touch:

Gityuan historical Reader diagram, including key-policy branches

Here is Android 9’s complete loopOnce() method. Notice that blocking reads and listener flush both happen outside the Reader lock. It remains system source, not a standalone app example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
void InputReader::loopOnce() {
int32_t oldGeneration;
int32_t timeoutMillis;
bool inputDevicesChanged = false;
Vector<InputDeviceInfo> inputDevices;
{ // acquire lock
AutoMutex _l(mLock);

oldGeneration = mGeneration;
timeoutMillis = -1;

uint32_t changes = mConfigurationChangesToRefresh;
if (changes) {
mConfigurationChangesToRefresh = 0;
timeoutMillis = 0;
refreshConfigurationLocked(changes);
} else if (mNextTimeout != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
}
} // release lock

size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

{ // acquire lock
AutoMutex _l(mLock);
mReaderIsAliveCondition.broadcast();

if (count) {
processEventsLocked(mEventBuffer, count);
}

if (mNextTimeout != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
if (now >= mNextTimeout) {
#if DEBUG_RAW_EVENTS
ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
#endif
mNextTimeout = LLONG_MAX;
timeoutExpiredLocked(now);
}
}

if (oldGeneration != mGeneration) {
inputDevicesChanged = true;
getInputDevicesLocked(inputDevices);
}
} // release lock

// Send out a message that the describes the changed input devices.
if (inputDevicesChanged) {
mPolicy->notifyInputDevicesChanged(inputDevices);
}

// Flush queued events out to the listener.
// This must happen outside of the lock because the listener could potentially call
// back into the InputReader's methods, such as getScanCodeState, or become blocked
// on another thread similarly waiting to acquire the InputReader lock thereby
// resulting in a deadlock. This situation is actually quite plausible because the
// listener is actually the input dispatcher, which calls into the window manager,
// which occasionally calls into the input reader.
mQueuedListener->flush();
}

InputDispatcher

Reader notifications may enqueue and wake Dispatcher; filtering and other wakeup sources also exist. The selected Runnable interval below records TID 25669, shown as InputReader, as its waker. That is evidence for this occurrence, not every Dispatcher wakeup.

One Dispatcher Runnable interval records wakeup from Reader TID 25669

Android 9 Dispatcher handles Key and Motion separately. Keys commonly involve focus and key policy; touch also involves hit testing, gesture targets, split/cancel behavior, and other conditions. The diagram below explicitly uses dispatchKeyLocked, findFocusedWindowTargetsLocked, and publishKeyEvent; it is not the exact touch path.

Gityuan historical Dispatcher diagram showing the Key branch

The following pseudocode describes the queue boundaries:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dispatchOnce:
obtain or continue the pending EventEntry
select eligible targets for the event type and window/touch state
enqueue a DispatchEntry for each selected connection
publish through that connection's InputPublisher/InputChannel
if publication succeeds:
move the entry from outboundQueue to waitQueue
otherwise:
handle backpressure or connection errors

on consumer FINISHED(seq, handled):
locate that connection's matching dispatch entry
process completion / possible fallback policy
retire or requeue the entry as required
continue pending dispatch work

That version sets deliveryTime while attempting publication in startDispatchCycleLocked(). It is not the hardware sample time, nor should the first dispatchOnce entry time be treated as every later delivery time despite intervening waits. Backpressure such as WOULD_BLOCK and connection errors require separate handling. Policy commands can run from a command queue at appropriate lock boundaries.

InboundQueue

notifyKey and notifyMotion may enqueue after their respective checks. Removing an entry into mPendingEvent reduces iq, while that event may still wait for a target or continue dispatch. iq=0 does not mean all system input work is finished.

iq counts inbound entries; dequeue does not imply completion

OutboundQueue

Each Connection has entries awaiting publication. One event can have multiple eligible targets or be filtered; an app can own multiple windows, and monitors have separate purposes. The selected counter below belongs to StatusBar, with value 1, not the entire Launcher input stream.

The selected StatusBar outbound connection has count 1

WaitQueue

After successful publication, Dispatcher moves the entry from outbound to wait. App finishInputEvent(event, handled) sends a native InputConsumer acknowledgment; Dispatcher processes it by connection/sequence and retires the matching entry, with fallback policy where applicable. See InputTransport and InputEventReceiver.

Sustained wait growth is a clue, with possible causes including scheduling, app work, MOVE batching, ACK transport/processing, and connection problems. Queue length is neither duration nor an ANR criterion. This next crop belongs to a separate sample.tencent.mat... list trace: the selection spans 32.276 ms, and one counter is 8. It is not the Launcher trace, nor proof that all eight entries have yet to enter the app.

A separate list case has wait count 8 within a 32.276ms selection

Overall Logic

This Gityuan diagram includes KeyboardInputMapper/notifyKey. It is a historical reading aid, not the exact Android 16 path for every touch.

Historical sequence diagram centered on KeyboardInputMapper and notifyKey

Input Refresh and Vsync

Does a higher touch sampling rate always help? First, separate touch sampling, reporting/dispatch cadence, display refresh rate, and actual app frame rate. 120Hz is about 8.33ms; 160Hz is 6.25ms. The original 8ms for 120Hz was approximate, and these are not universal current-device specifications.

Historical annotation of roughly 160Hz sampling and wait changes during MOVE batching

I labeled this device 160Hz in the capture at the time, but Reader execution spacing is also affected by report batching and scheduling. Temporary WaitQueue accumulation does not mean the app never consumes input. Batched MOVE may be consumed in Choreographer’s INPUT phase against frame time; DOWN/UP cannot be generalized to the same rule.

Multiple samples are not simply reduced to the latest with all others discarded. The MotionEvent batching contract allows ACTION_MOVE to contain historical samples, read before current coordinates. Android 9 InputConsumer.consumeBatch/consumeSamples() selects the appropriate sample-time range, builds MotionEvent history, and maintains sequence chains for acknowledgment. Enabled resampling can also estimate positions relative to frame time.

A 60Hz display therefore does not prove 120Hz touch is sufficient, nor does 90Hz require exactly 180Hz touch. Higher sampling can reduce some waits or provide finer data, with processing/power costs. Alternating groups of two/three samples do not themselves prove visible jitter. Compare event times, history/resampling, presentation, and end-to-end latency rather than requiring an exact 2:1 ratio.

Input Debug Information

1
adb shell dumpsys input

This command produces a lot of output. I picked out the Device, InputReader, and InputDispatcher sections below from the device snapshot I took at the time. Fields/formats depend on the system, and this is not a continuous record of an interaction. Interpret unknowns, empty fields, and sentinel values according to their meaning.

Device Info

Paths, descriptors, IDs, and mapping files belong to this snapshot. event6 is not every phone’s touch node, and these illustrative IDs alone do not identify a manufacturer.

1
2
3
4
5
6
7
8
9
10
11
12
13
    3: main_touch
      Classes: 0x00000015
      Path: /dev/input/event6
      Enabled: true
      Descriptor: 4055b8a032ccf50ef66dbe2ff99f3b2474e9eab5
      Location: main_touch/input0
      ControllerNumber: 0
      UniqueId: 
      Identifier: bus=0x0000, vendor=0xbeef, product=0xdead, version=0x28bb
      KeyLayoutFile: /system/usr/keylayout/main_touch.kl
      KeyCharacterMapFile: /system/usr/keychars/Generic.kcm
      ConfigurationFile: 
      HaveKeyboardLayoutOverlay: false

Input Reader Status

Raw (660, 1338) and cooked (659.389, 1337.401) reflect coordinate mapping/calibration, not identical coordinate spaces. PhysicalWidth here is explicitly in px, not millimeters. unknown range is not a zero range. The external-stylus 9223372036854775807 values are old-state sentinels such as no active deadline, not real input times.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
  Device 3: main_touch
    Generation: 24
    IsExternal: false
    HasMic:     false
    Sources: 0x00005103
    KeyboardType: 1
    Motion Ranges:
      X: source=0x00005002, min=0.000, max=1079.000, flat=0.000, fuzz=0.000, resolution=0.000
      Y: source=0x00005002, min=0.000, max=2231.000, flat=0.000, fuzz=0.000, resolution=0.000
      PRESSURE: source=0x00005002, min=0.000, max=1.000, flat=0.000, fuzz=0.000, resolution=0.000
      SIZE: source=0x00005002, min=0.000, max=1.000, flat=0.000, fuzz=0.000, resolution=0.000
      TOUCH_MAJOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000
      TOUCH_MINOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000
      TOOL_MAJOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000
      TOOL_MINOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000
    Keyboard Input Mapper:
      Parameters:
        HasAssociatedDisplay: false
        OrientationAware: false
        HandlesKeyRepeat: false
      KeyboardType: 1
      Orientation: 0
      KeyDowns: 0 keys currently down
      MetaState: 0x0
      DownTime: 521271703875000
    Touch Input Mapper (mode - direct):
      Parameters:
        GestureMode: multi-touch
        DeviceType: touchScreen
        AssociatedDisplay: hasAssociatedDisplay=true, isExternal=false, displayId=''
        OrientationAware: true
      Raw Touch Axes:
        X: min=0, max=1080, flat=0, fuzz=0, resolution=0
        Y: min=0, max=2232, flat=0, fuzz=0, resolution=0
        Pressure: min=0, max=127, flat=0, fuzz=0, resolution=0
        TouchMajor: min=0, max=512, flat=0, fuzz=0, resolution=0
        TouchMinor: unknown range
        ToolMajor: unknown range
        ToolMinor: unknown range
        Orientation: unknown range
        Distance: unknown range
        TiltX: unknown range
        TiltY: unknown range
        TrackingId: min=0, max=65535, flat=0, fuzz=0, resolution=0
        Slot: min=0, max=20, flat=0, fuzz=0, resolution=0
      Calibration:
        touch.size.calibration: geometric
        touch.pressure.calibration: physical
        touch.orientation.calibration: none
        touch.distance.calibration: none
        touch.coverage.calibration: none
      Affine Transformation:
        X scale: 1.000
        X ymix: 0.000
        X offset: 0.000
        Y xmix: 0.000
        Y scale: 1.000
        Y offset: 0.000
      Viewport: displayId=0, orientation=0, logicalFrame=[0, 0, 1080, 2232], physicalFrame=[0, 0, 1080, 2232], deviceSize=[1080, 2232]
      SurfaceWidth: 1080px
      SurfaceHeight: 2232px
      SurfaceLeft: 0
      SurfaceTop: 0
      PhysicalWidth: 1080px
      PhysicalHeight: 2232px
      PhysicalLeft: 0
      PhysicalTop: 0
      SurfaceOrientation: 0
      Translation and Scaling Factors:
        XTranslate: 0.000
        YTranslate: 0.000
        XScale: 0.999
        YScale: 1.000
        XPrecision: 1.001
        YPrecision: 1.000
        GeometricScale: 0.999
        PressureScale: 0.008
        SizeScale: 0.002
        OrientationScale: 0.000
        DistanceScale: 0.000
        HaveTilt: false
        TiltXCenter: 0.000
        TiltXScale: 0.000
        TiltYCenter: 0.000
        TiltYScale: 0.000
      Last Raw Button State: 0x00000000
      Last Raw Touch: pointerCount=1
        [0]: id=0, x=660, y=1338, pressure=44, touchMajor=44, touchMinor=44, toolMajor=0, toolMinor=0, orientation=0, tiltX=0, tiltY=0, distance=0, toolType=1, isHovering=false
      Last Cooked Button State: 0x00000000
      Last Cooked Touch: pointerCount=1
        [0]: id=0, x=659.389, y=1337.401, pressure=0.346, touchMajor=43.970, touchMinor=43.970, toolMajor=43.970, toolMinor=43.970, orientation=0.000, tilt=0.000, distance=0.000, toolType=1, isHovering=false
      Stylus Fusion:
        ExternalStylusConnected: false
        External Stylus ID: -1
        External Stylus Data Timeout: 9223372036854775807
      External Stylus State:
        When: 9223372036854775807
        Pressure: 0.000000
        Button State: 0x00000000
        Tool Type: 0

InputDispatcher Status

Distinguish focused application from focused window, per-display touch targets, window entries, monitors, and connections. MonitoringChannels is not the list of ordinary channels for every window, and this is an excerpt. DispatchFrozen describes input dispatch state, not the later cached-app freezer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Input Dispatcher State:
  DispatchEnabled: 1
  DispatchFrozen: 0
  FocusedApplication: name='AppWindowToken{ac6ec28 token=Token{a38a4b ActivityRecord{7230f1a u0 com.meizu.flyme.launcher/.Launcher t13}}}', dispatchingTimeout=5000.000ms
  FocusedWindow: name='Window{3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher}'
  TouchStatesByDisplay:
    0: down=true, split=true, deviceId=3, source=0x00005002
      Windows:
        0: name='Window{3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher}', pointerIds=0x80000000, targetFlags=0x105
        1: name='Window{8cb8f7 u0 com.android.systemui.ImageWallpaper}', pointerIds=0x0, targetFlags=0x4102
  Windows:
    2: name='Window{ba2fc6b u0 NavigationBar}', displayId=0, paused=false, hasFocus=false, hasWallpaper=false, visible=true, canReceiveKeys=false, flags=0x21840068, type=0x000007e3, layer=0, frame=[0,2136][1080,2232], scale=1.000000, touchableRegion=[0,2136][1080,2232], inputFeatures=0x00000000, ownerPid=26514, ownerUid=10033, dispatchingTimeout=5000.000ms
    3: name='Window{72b7776 u0 StatusBar}', displayId=0, paused=false, hasFocus=false, hasWallpaper=false, visible=true, canReceiveKeys=false, flags=0x81840048, type=0x000007d0, layer=0, frame=[0,0][1080,84], scale=1.000000, touchableRegion=[0,0][1080,84], inputFeatures=0x00000000, ownerPid=26514, ownerUid=10033, dispatchingTimeout=5000.000ms
    9: name='Window{3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher}', displayId=0, paused=false, hasFocus=true, hasWallpaper=true, visible=true, canReceiveKeys=true, flags=0x81910120, type=0x00000001, layer=0, frame=[0,0][1080,2232], scale=1.000000, touchableRegion=[0,0][1080,2232], inputFeatures=0x00000000, ownerPid=27619, ownerUid=10021, dispatchingTimeout=5000.000ms
  MonitoringChannels:
    0: 'WindowManager (server)'
  RecentQueue: length=10
    MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (524.5, 1306.4)]), policyFlags=0x62000000, age=61.2ms
    MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (543.5, 1309.4)]), policyFlags=0x62000000, age=54.7ms
  PendingEvent: <none>
  InboundQueue: <empty>
  ReplacedKeys: <empty>
  Connections:
    0: channelName='WindowManager (server)', windowName='monitor', status=NORMAL, monitor=true, inputPublisherBlocked=false
      OutboundQueue: <empty>
      WaitQueue: <empty>
    5: channelName='72b7776 StatusBar (server)', windowName='Window{72b7776 u0 StatusBar}', status=NORMAL, monitor=false, inputPublisherBlocked=false
      OutboundQueue: <empty>
      WaitQueue: <empty>
    6: channelName='ba2fc6b NavigationBar (server)', windowName='Window{ba2fc6b u0 NavigationBar}', status=NORMAL, monitor=false, inputPublisherBlocked=false
      OutboundQueue: <empty>
      WaitQueue: <empty>
    12: channelName='3c007ad com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher (server)', windowName='Window{3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher}', status=NORMAL, monitor=false, inputPublisherBlocked=false
      OutboundQueue: <empty>
      WaitQueue: length=3
        MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (634.4, 1329.4)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=2, age=17.4ms, wait=16.8ms
        MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (647.4, 1333.4)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=2, age=11.1ms, wait=10.4ms
        MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (659.4, 1337.4)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=2, age=5.2ms, wait=4.6ms
  AppSwitch: not pending
  Configuration:
    KeyRepeatDelay: 50.0ms
    KeyRepeatTimeout: 500.0ms

This snapshot has three waiting MOVE entries. age measures from event time to dump time; wait measures from that entry’s deliveryTime to dump time. Neither is total visual response latency. Its 5000.000ms is a reported dispatch timeout, not every device’s or every ANR type’s universal limit. The official input-dispatch ANR guide describes a default of 5 seconds and cases such as no focused window. Correlate timeout logs, targets, and blocking causes; three outstanding events do not themselves establish an ANR.

Continuing on Newer Versions

AOSP Android 16 InputDispatcher has a Perfetto input-event tracing backend, but that tag gates it on userdebug/eng builds and the relevant feature flag. Do not promise that ordinary user builds can obtain android.input.inputevent by toggling one setting.

With the required ATrace data, Perfetto’s android.input module can break down send, receive, handling, and ACK round trips. Structured android.input.inputevent is a separate source with its own fields; neither is guaranteed present. Frame associations may be missing or speculative. ACK round trip is not complete physical touch-to-display latency, and unavailable data is not zero latency.

References

Portions of this article reference Gityuan’s blog. These articles provide deep dives into code details for those interested after finishing this piece.

  1. http://gityuan.com/2016/12/11/input-reader/
  2. http://gityuan.com/2016/12/10/input-manager/
  3. http://gityuan.com/2016/12/17/input-dispatcher/
  4. https://zhuanlan.zhihu.com/p/29386642

Attachments

The directory contains trace_input_blog_launcher_scroll.html.zip and trace_input_blog_list_jank.html.zip, respectively the Launcher and list-jank cases. Both readable archives contain HTML with an old viewer, process dumps, and ftrace data, targeting different apps. Current-browser compatibility of the embedded viewer was not tested here. A tool supporting legacy Systrace data can also be used to import and inspect the events:
Download Systrace attachments for this article

Other Addresses

For comments or likes, please visit the Zhihu or Juejin pages for this article:
Juejin - Systrace Basics - Input Interpretation

About Me && Blog

Below is my personal intro and related links. I look forward to exchanging ideas with fellow professionals. “When three walk together, one can always be my teacher!”

  1. Blogger Intro: Includes personal WeChat and WeChat group links.
  2. Blog Content Navigation: A guide for my blog content.
  3. Curated Excellent Blog Articles - Android Performance Optimization Must-Knows: Welcome to recommend projects/articles.
  4. Android Performance Optimization Knowledge Planet: The paid community stopped operating in 2026 and no longer accepts new members; this link preserves its history.

One walks faster alone, but a group walks further together.

Scan WeChat QR Code

CATALOG
  1. 1. Table of Contents
  • Series Article Index
  • Main Content
  • Input in Systrace
    1. 1. InputDown Event Workflow in SystemServer
    2. 2. InputDown Event Workflow in App
      1. 2.1. App’s Pending Queue
      2. 2.2. Main Thread Processing Input Events
  • Key Knowledge Points and Processes
    1. 1. InputReader
    2. 2. InputDispatcher
    3. 3. InboundQueue
    4. 4. OutboundQueue
    5. 5. WaitQueue
    6. 6. Overall Logic
  • Input Refresh and Vsync
  • Input Debug Information
    1. 1. Device Info
    2. 2. Input Reader Status
    3. 3. InputDispatcher Status
    4. 4. Continuing on Newer Versions
  • References
  • Attachments
  • Other Addresses
  • About Me && Blog