First half of the approved intents feature (docs/INTENTS_PLAN.md §2): the scan
that answers "what can this app actually be asked to do". No UI is wired here —
this is the discovery module, the manager path it rides on, and the tests.
THE DUMP IS FILTERED ON THE DEVICE. On the user's Shield `pm dump
com.netflix.ninja` is ~2241 lines of whole dumpsys for ONE app, so shipping it
over ADB to read a few dozen resolver lines repeats the mistake `| head -300`
made for app labels (t66 — where a truncation artefact was read as "this app has
no label"). The fetch now sends:
pm dump <pkg> 2>/dev/null | sed -n "/Activity Resolver Table/,/Service Resolver Table/p" | head -n 400
with a bounded `grep -A 400 -m 1 -F 'Activity Resolver Table:'` fallback for a
device whose toolbox cannot do sed ranges, tried only when the primary answer did
not contain the section. The package id is the only caller-supplied text in
either command and is always shlex.quote'd, and the sed script is built from two
constants — tests drive eight hostile package ids (semicolons, &&, backticks,
$(), pipes, both quote kinds, a newline) and assert that the shell sees the
package as exactly one verbatim token and that no extra command token appears.
WHAT A CANDIDATE CARRIES: component, action, categories and the data
(scheme/authority/path, plus the matcher kind — `/title.*` is a GLOB, not a
path, and a caller that treats it as literal builds the wrong URI). A filter
carrying several schemes/authorities/paths expands to one candidate per
combination, because each is a concrete match, and dedup collapses the
coincidences. MIME keys are deliberately NOT modelled (they are not one of §3's
fields) — such a filter is still reported through its component, action and
categories rather than being invented into a URI.
RANK, NEVER HIDE. The four families the plan names as internal-looking noise
(DEBUG_*, androidx.*, com.google.firebase.*, ConstraintProxy*) are FLAGGED, not
deleted: they sort last so the useful rows come first, and the user still
decides. Ordering is total (flag, action, scheme, authority, path, component,
categories) so a re-scan is byte-identical, and the 200-candidate cap is applied
after dedup and DISCLOSED through truncated/found — a capped answer never looks
complete.
THE CACHE KEEPS AN ANSWER AND A FAILURE APART, structurally: put() is its only
writer, only a scan that RAN calls it, and it derives the entry's state — an
empty candidate list is INTENT_ENTRY_EMPTY, a real ANSWER the device gave, while
a scan whose transport failed caches nothing and is retried. A stale entry is
dropped on read rather than served. The scan itself has one status per outcome
(fresh / fresh_empty / cached / adb_disabled / adb_unavailable / unreadable /
invalid_package) so "ADB is off", "the device did not answer" and "this app
advertises nothing" can never be confused, and the not-answered statuses carry no
candidates at all — an ADB-down scan cannot look like an app with no intents.
ADB disabled and ADB unreachable are told apart from the entry's own option.
Discovery is EXPLICIT and single: one package per request, never a sweep over the
installed apps, and a test drives a watchdog cycle and asserts it starts no scan.
Concurrent requests for the same package share one dump.
THE FIXTURE IS A TRANSCRIPTION, NOT IMAGINATION. tests/fixtures/
pm_dump_netflix_resolver.txt reproduces the user's 2026-10-12 capture structure
(whole-dumpsys envelope, the activity table with its Schemes/Non-Data Actions
groups, the receiver/service/provider tables that bound it) and the test file's
docstring states line by line what is VERBATIM from the ticket/plan and what is
reconstructed — no real Shield was available, and the raw capture is not in this
repository (the same limit t13 recorded). The capture's own entries are each
pinned by an assertion: the cast entry, http/https with the four categories and
the five www.netflix.com path globs, nflx:, netflix:, MAIN with LEANBACK_LAUNCHER
+ LAUNCHER, NETFLIX_KEY_START, DIAL_START and the two DEBUG_* noise actions —
18 candidates, deduplicated, with the receiver/provider tables proven unread.
Evidence: 59 new tests in tests/test_intents.py (parser against the transcribed
capture, degradation on shapes never seen, the command shape and quoting safety,
the cache states and TTL, and the manager: one shell, cached, retried on failure,
fallback recovery, ADB-off versus ADB-down, the cap disclosed, the watchdog
silent, concurrent sharing). No device was available: the parser is proven
against the transcription, and every "runs/does not run" claim is an assertion on
the poller's recorded commands rather than on intentions.
Verify: suites 778/4 and 779/3; ruff clean; drift exit 0 (26 modules, 76 imports,
242 relative imports, 1417 statement blocks, 57 call arguments) with the
relative-import floor raised 195 -> 235 and the statement-block floor 1300 ->
1400 by the new module rather than dodged; the real-HA glob still runs green
(56 passed, 0 skipped).
102 lines
4.3 KiB
Plaintext
102 lines
4.3 KiB
Plaintext
DUMP OF SERVICE package:
|
|
Activity Resolver Table:
|
|
Schemes:
|
|
cast: deca7fa com.netflix.ninja/.MainActivity filter fc483a1
|
|
Action: "com.google.cast.action.START"
|
|
Category: "android.intent.category.DEFAULT"
|
|
Scheme: "cast"
|
|
http: deca7fa com.netflix.ninja/.MainActivity filter fc483a1
|
|
Action: "android.intent.action.VIEW"
|
|
Category: "android.intent.category.DEFAULT"
|
|
Category: "android.intent.category.BROWSABLE"
|
|
Category: "android.intent.category.LEANBACK_LAUNCHER"
|
|
Category: "android.intent.category.LAUNCHER"
|
|
Scheme: "http"
|
|
Authority: "www.netflix.com": -1
|
|
Path: "PatternMatcher{GLOB: /title.*}"
|
|
Path: "PatternMatcher{GLOB: /watch.*}"
|
|
Path: "PatternMatcher{LITERAL, /browse}"
|
|
Path: "PatternMatcher{LITERAL, /home}"
|
|
Path: "PatternMatcher{GLOB: /deeplink.*}"
|
|
https: deca7fa com.netflix.ninja/.MainActivity filter fc483a1
|
|
Action: "android.intent.action.VIEW"
|
|
Category: "android.intent.category.DEFAULT"
|
|
Category: "android.intent.category.BROWSABLE"
|
|
Category: "android.intent.category.LEANBACK_LAUNCHER"
|
|
Category: "android.intent.category.LAUNCHER"
|
|
Scheme: "https"
|
|
Authority: "www.netflix.com": -1
|
|
Path: "PatternMatcher{GLOB: /title.*}"
|
|
Path: "PatternMatcher{GLOB: /watch.*}"
|
|
Path: "PatternMatcher{LITERAL, /browse}"
|
|
Path: "PatternMatcher{LITERAL, /home}"
|
|
Path: "PatternMatcher{GLOB: /deeplink.*}"
|
|
netflix: deca7fa com.netflix.ninja/.MainActivity filter fc483a1
|
|
Action: "android.intent.action.VIEW"
|
|
Category: "android.intent.category.DEFAULT"
|
|
Scheme: "netflix"
|
|
nflx: deca7fa com.netflix.ninja/.MainActivity filter fc483a1
|
|
Action: "android.intent.action.VIEW"
|
|
Category: "android.intent.category.DEFAULT"
|
|
Scheme: "nflx"
|
|
Non-Data Actions:
|
|
android.intent.action.MAIN:
|
|
deca7fa com.netflix.ninja/.MainActivity filter fc483a2
|
|
Action: "android.intent.action.MAIN"
|
|
Category: "android.intent.category.LEANBACK_LAUNCHER"
|
|
Category: "android.intent.category.LAUNCHER"
|
|
com.netflix.action.NETFLIX_KEY_START:
|
|
deca7fa com.netflix.ninja/.MainActivity filter fc483a3
|
|
Action: "com.netflix.action.NETFLIX_KEY_START"
|
|
com.netflix.action.DIAL_START:
|
|
deca7fa com.netflix.ninja/.MainActivity filter fc483a4
|
|
Action: "com.netflix.action.DIAL_START"
|
|
com.netflix.action.DEBUG_PUSH_NOTIFICATION:
|
|
deca7fa com.netflix.ninja/.debug.DebugReceiver filter fc483a5
|
|
Action: "com.netflix.action.DEBUG_PUSH_NOTIFICATION"
|
|
com.netflix.action.DEBUG_FORCE_EXIT:
|
|
deca7fa com.netflix.ninja/.debug.DebugReceiver filter fc483a6
|
|
Action: "com.netflix.action.DEBUG_FORCE_EXIT"
|
|
Receiver Resolver Table:
|
|
Non-Data Actions:
|
|
androidx.work.DiagnosticsReportReceiver:
|
|
deca7fa com.netflix.ninja/androidx.work.impl.diagnostics.DiagnosticsReceiver
|
|
com.google.firebase.MESSAGING_EVENT:
|
|
deca7fa com.netflix.ninja/com.google.firebase.messaging.FirebaseMessagingService
|
|
com.netflix.action.DEBUG_PUSH_NOTIFICATION:
|
|
deca7fa com.netflix.ninja/.push.DebugPushReceiver
|
|
Service Resolver Table:
|
|
Non-Data Actions:
|
|
com.google.firebase.INSTANCE_ID_EVENT:
|
|
deca7fa com.netflix.ninja/com.google.firebase.iid.FirebaseInstanceIdService
|
|
Provider Resolver Table:
|
|
Authorities:
|
|
com.netflix.ninja.androidx-startup:
|
|
deca7fa com.netflix.ninja/androidx.startup.InitializationProvider
|
|
Permissions:
|
|
Permission [com.netflix.ninja.C2D_MESSAGE]:
|
|
sourcePackage=com.netflix.ninja
|
|
Packages:
|
|
Package [com.netflix.ninja] (7f1a2b3c):
|
|
userId=10123
|
|
pkg=Package{7f1a2b3c com.netflix.ninja}
|
|
nonLocalizedLabel=null
|
|
labelRes=0x7f0f0d31
|
|
versionCode=61102 minSdk=21 targetSdk=29
|
|
versionName=6.11.2 build 3413
|
|
codePath=/data/app/com.netflix.ninja-1/base.apk
|
|
ConstraintProxy.BatteryBroadcastReceiver=
|
|
com.netflix.ninja/androidx.work.impl.constraints.trackers.BatteryChargingProxy
|
|
Activity Resolver Table: (nothing else listed for this package)
|
|
DUMP OF SERVICE activity:
|
|
ACTIVITY MANAGER PENDING INTENTS (dumpsys activity pending)
|
|
(nothing)
|
|
DUMP OF SERVICE meminfo:
|
|
Applications Memory Usage (in Kilobytes):
|
|
DUMP OF SERVICE procstats:
|
|
Process Stats:
|
|
DUMP OF SERVICE usagestats:
|
|
In-memory rollup:
|
|
DUMP OF SERVICE batterystats:
|
|
Battery Stats:
|