installAutographNativeTapCapture

fun installAutographNativeTapCapture(application: Application, tracker: Tracker, scopeStack: ScopeStack, eventName: String = DEFAULT_AUTOCAPTURE_EVENT_NAME): AutographNativeTapCapture

Starts reporting taps on native Android View content (XML layouts, RecyclerView rows, legacy screens) as eventName. This is the Android counterpart of the iOS UIKit tap capture, and the pipeline autograph-compose cannot serve: Compose autocapture hit-tests the semantics tree, which does not see an android.view.View hierarchy at all.

Opt-in, exactly as Compose autocapture is: observing every tap is a different privacy posture than instrumenting individual elements, so nothing happens until an app calls this.

Pass the same scopeStack the app gives AutographProvider and installAutographNativeScreenCapture. Sharing one stack is what lets a View-screen tap carry the screen and scope a Compose screen pushed, and vice versa.

How a tap is resolved, and why it is not a hit test

Every touch is seen by wrapping the Activity's Window.Callback, which delegates everything unchanged — nothing is consumed, delayed or reordered. On touch-up the target is the root of the pressed subtree: touch dispatch marks the view it delivered to as pressed, so reading that back inherits sibling z-ordering, enabled state, gesture stealing by a scrolling parent, and consumption, rather than re-deriving them geometrically. Measured against a geometric walk on the same taps, the walk named the wrong sibling of an elevation-reordered pair, named a disabled button no click reached, and named an element at the end of a scroll; this rule matched what actually fired in every case. See resolveTapTarget.

target is the view's resource entry name and nothing else — never displayed text, never a contentDescription. A view with no id reports nothing.

Read that as "the entry name of the view that was pressed", not as "a name the app author chose". Only the android package can be excluded, because it is the only one still distinguishable at runtime: an AAR's resources are merged into the application package at build time, so a Material or AppCompat id looks exactly like one of the app's own and is reported the same way.

Excluding a region, and when you have to

Set isAutographIgnored on a view to exclude it and everything under it, the same way Modifier.autographIgnore() excludes a Compose subtree and registerAutographIgnoredView excludes a UIView. Marking a container is enough; there is no need to mark each clickable inside it. It is a settable property rather than a one-way mark because RecyclerView recycles views — see its own documentation.

A screen with sensitive fields needs it. An editable TextView is clickable, so a tap that merely focuses @+id/password or @+id/card_cvv reports that id. Nothing here infers which fields are sensitive; mark them, or the region holding them.

What is not captured — none of it silently, all of it by construction

  • Taps that never travel through touch dispatch. A View whose OnTouchListener consumes the gesture and calls performClick() itself (the pattern Android lint recommends for custom views), a GestureDetector-driven custom view, and — the case worth stating loudest — a click made with a keyboard, D-pad, or an accessibility service's ACTION_CLICK. Those never set the pressed state and are invisible here. The last of them means this capture's silence is not evenly distributed across users: someone driving the app with TalkBack or a keyboard produces no taps at all, which downstream is indistinguishable from not tapping.

  • Dialogs, PopupWindows, and everything built on them — a Spinner's dropdown, an overflow menu, an AutoCompleteTextView's suggestions. Each renders in a window this capture is not on. This is a deliberate boundary rather than an impossibility: the routes that would cover them are reflection into framework internals (what Curtains does) or an explicit per-window registration API, and neither is in this release.

  • Compose content, which has its own pipeline. Nothing special is done to exclude it and none is needed: Compose routes pointer input itself and never sets the View pressed state, so a tap on a ComposeView resolves to nothing here and is reported exactly once, by autograph-compose.

  • Multi-touch on separate elements. At most one event is sent per gesture — the pressed state is global to the hierarchy rather than per pointer, so two touch-ups of one gesture cannot be told apart and reporting each separately would double-count a press whenever a second finger was merely resting on the screen. With two fingers genuinely down on two views, that means at most one of them is reported and which one is not guaranteed: it depends on whether the framework's posted press-release runnable runs between the two touch-ups, which is input batching.

  • A ListView row reports the list, not the row. AbsListView presses both, and the row of a platform row layout carries a platform id (text1) shared by every list in the app, so the list's own id is the better of the two available answers. A RecyclerView row is an ordinary pressed view and reports its own id.

One known misreport, kept deliberately: a long press consumed by an OnLongClickListener reports as a tap on that element. The framework's own "the long press was handled" flag is private, and the only public proxy — press duration — would drop real clicks, because a long press on a view whose listener returns false does fire a click. The element named is correct; the gesture kind is not.

Only Activities that resume after this call are covered

registerActivityLifecycleCallbacks does not replay, and the only wrap sites are the resumed callbacks, so an Activity already on screen when this is called is never wrapped: every tap on it is dropped until the user navigates away and back, and no diagnostic fires, because the capture never sees the touch at all. Install from Application.onCreate() — no Activity exists yet, so nothing is missed. Installing later (behind a consent prompt, after a remote-config fetch) is the shape that silently loses the first screen. Same limitation, and same remedy, as installAutographNativeScreenCapture.

Threading

Main thread only, to install, to AutographNativeTapCapture.uninstall, and throughout — the lifecycle callbacks and touch dispatch are both delivered there.

Keep the returned handle: it is the only way to AutographNativeTapCapture.uninstall. Note that uninstalling stops the reporting but does not drop the references: a retired wrapper stays in its window's callback chain (see AutographNativeTapCapture.uninstall for why it must), and it holds this capture — and so tracker and scopeStack — until that window is destroyed.