Skip to content

将node.performAction(AccessibilityNodeInfo.ACTION_CLICK)都改为click,保证所有应用都能处理 - #90

Open
cyqw wants to merge 1 commit into
zfdang:masterfrom
cyqw:master
Open

cyqw wants to merge 1 commit into
zfdang:masterfrom
cyqw:master

Conversation

@cyqw

@cyqw cyqw commented Dec 17, 2023 •

Copy link
Copy Markdown

zfdang added a commit that referenced this pull request Sep 22, 2026
* escalate from ACTION_CLICK to a touch gesture when a skip button survives

performAction(ACTION_CLICK) returning true only means the view handled
the event; some ad SDKs ignore accessibility clicks, and a node that is
not laid out yet (bounds 0,0,0,0, seen with Zhihu on a real device)
reports success without any effect. PR #90 proposed replacing every
ACTION_CLICK with a coordinate gesture, which handles those apps but
taps the origin for unlaid-out nodes, can hit whatever replaced the ad
when it closed a moment earlier, and drops the onlyClick rule flag.

This keeps ACTION_CLICK (node, then its parent) as the first attempt,
falls back to a gesture immediately when ACTION_CLICK is rejected, and
otherwise escalates to a gesture only when the same widget is still on
screen at least 500 ms later. A widget is attempted at most twice per
skip-ad process. Widgets are identified by class, view id and bounds so
that a changing countdown label ("跳过 5" -> "跳过 4") does not reset the
attempt count. Gestures are only dispatched at a non-empty, on-screen,
visible spot. The onlyClick flag of widget rules still forces a gesture.

Adds Robolectric tests for the escalation, the immediate fallback, the
parent click, invalid coordinates, onlyClick and attempt reset; 47 unit
tests in total.

* handle warm-start ads after the screen was unlocked

Apps often show a splash ad when they come back to the foreground after
the screen was locked. The existing hook only reacted to USER_PRESENT,
restarted the skip-ad process for whatever package was last recorded,
even a whitelisted app or the launcher, and then waited passively for
events.

- detect the warm start from the accessibility events: a system window
  shown while the keyguard is locked (or the screen is off) marks the
  current target app as covered, and the next window of that app
  restarts the skip-ad process. On OPPO devices the system skips
  USER_PRESENT/SCREEN_ON deliveries to third-party apps ("oplus skip sp"),
  so this path does not depend on broadcasts at all
- the broadcast receiver stays as an additional trigger and now also
  listens for SCREEN_ON (devices without a lock screen never send
  USER_PRESENT)
- both paths only act when the foreground app is one we handle, and
  scan its window right away and once more a second later instead of
  waiting for further events

Adds Robolectric tests for the unlock broadcast, SCREEN_ON, unhandled
apps, a foreign active window, the event-based detection and its
negatives (notification shade, the app's own dialogs); 55 unit tests.

* refresh nodes before gestures and fix retry bookkeeping found in review

- a gesture is only dispatched after AccessibilityNodeInfo.refresh()
  succeeds and the refreshed node still belongs to a target package,
  still matches its rule, and is a visible spot on screen. A queued
  snapshot of a button whose ad has already closed is therefore never
  tapped at its old coordinates
- widget rules stay active until the matched widget has had its final
  attempt, so a button that only matches by id can still be escalated
  to a gesture at the next sighting
- click attempts are counted only when ACTION_CLICK or a gesture was
  actually issued; a gesture refused by the checks (e.g. the widget is
  not visible yet) leaves the attempt budget untouched
- the pending warm start is kept while the device is still locked: a
  same-package window during the lock screen, or SCREEN_ON with the
  keyguard up, no longer starts a process that expires before the user
  unlocks

Adds regression tests for each case; 61 unit tests in total.

* keep in-flight clicks and scans from touching a newer skip-ad process

startSkipAdProcess() clears the per-process click attempts. A click
whose performAction() was still blocking on the worker when the user
left and re-entered the app then recorded itself into the fresh table,
so the same button in the new ad was either held back by the retry
interval or escalated straight to a gesture.

Every startSkipAdProcess() now bumps a generation counter. A traversal
stops as soon as the generation changes, and a click only records its
attempt when it completes under the generation it started in.

Adds two controlled-concurrency tests; 63 unit tests in total.

* give every skip-ad process its own click session

The generation check before recording a click attempt was still a
check-then-act: the worker could pass the check, get descheduled, and
write into the table that startSkipAdProcess() had cleared in between.

Click state now lives in a ClickSession object. startSkipAdProcess()
installs a fresh session; a traversal captures the session it started
under and passes it to every click, so an attempt is recorded in that
session no matter when the click completes. A click that finishes after
the user re-entered the app lands in the discarded old session and can
never hold back or escalate the first click of the new ad. The stale
traversal itself still stops as soon as the session changes.

Test asserts the record ends up in the old session and the new one stays
empty; 63 unit tests in total.

* keep the widget-rules-done flag per skip-ad process

The final attempt of a widget rule cleared the shared setTargetedWidgets
field. When that attempt completed after the user had re-entered the
app, it switched off the widget rules of the new process, which reuses
the same rule set.

The flag now lives in the ClickSession the scan started under and only
that session's rules are considered done; scheduling reads the rules
through activeWidgetRules(), which hides them once the current session
is finished.

Test blocks the old process inside its final gesture, starts a new
process and checks the rules stay active; 64 unit tests in total.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant