Files
readest/apps/readest-app/.claude/memory/dict-lookup-browser-hijack-4559.md
T
2026-06-13 08:57:21 +02:00

3.3 KiB

name, description, metadata
name description metadata
dict-lookup-browser-hijack-4559 Android system-dictionary lookup landing in the OEM browser instead of Eudic/欧路 — package-visibility + PROCESS_TEXT browser hijack
node_type type originSessionId
memory project 5e397668-b766-439c-873c-00ccb1da715a

#4559 (PR #4568): on VIVO/iQOO (OriginOS) the system-dictionary lookup opened com.vivo.browser/.BrowserActivity instead of an installed dictionary. TWO root causes, both in the Android half of show_lookup_popover (tauri-plugin-native-bridge/.../NativeBridgePlugin.kt):

  1. Package-visibility filtering (primary). App is targetSdk 36, but the native-bridge manifest had NO <queries> for ACTION_PROCESS_TEXT. Under Android 11+ filtering, queryIntentActivities(PROCESS_TEXT) then returns only auto-visible apps — web browsers are auto-visible (web-intent exception), arbitrary dictionary apps (Eudic/欧路/GoldenDict) are NOT. So the query returned just the browser. Fix = add <queries><intent><action PROCESS_TEXT/><data text/plain/></intent></queries> to the plugin manifest (mirrors the native-tts TTS_SERVICE pattern). This alone is likely the whole user-visible fix.
  2. Browser hijack. Even when visible, an OEM browser registering ACTION_PROCESS_TEXT can be the system default and swallow a plain startActivity. Fix = filter browsers out of the handler set in a pure decideLookupDispatch(handlers, browserPackages, remembered) (new LookupDispatch.kt, JUnit-tested): no-browser → implicit (unchanged, keeps native "Always"); browser+1 dict → explicit setClassName direct launch; browser+≥2 → createChooser + EXTRA_EXCLUDE_COMPONENTS; browser-only → unavailable:true. Browsers detected via queryIntentActivities(ACTION_VIEW https + BROWSABLE) (auto-visible, no <queries> needed).

Remember-the-choice (the maintainer wanted "smooth once chosen"): ACTION_CHOOSER has NO native "Always" button (mutually exclusive with EXTRA_EXCLUDE_COMPONENTS — the resolver that has Always can't exclude and obeys the browser default). Re-implemented Always: pass an IntentSender to createChooser; system returns EXTRA_CHOSEN_COMPONENT to a manifest LookupChoiceReceiver (exported=false; explicit intra-app PendingIntent so non-exported is fine; FLAG_MUTABLE on S+) which persists pkg/class to a plain SharedPreferences (readest_lookup_dictionary_v1). Next lookup fast-paths it. Reset UI: get_lookup_dictionary/clear_lookup_dictionary commands (build.rs COMMANDS + default.toml + autogenerated TOMLs/schema/reference regenerate on cargo build -p tauri-plugin-native-bridge) → Android-only conditional reset row in CustomDictionaries.tsx, only shown when something is actually remembered (getRememberedLookupApp returns null otherwise, so no clutter).

Maintainer DECLINED a settings picker to pre-pick a specific app ("we won't call the app directly"); browser-exclusion respects that (dynamic, not a user-set hardcode). Verified: gradle JUnit (7 cases) + vitest (12). test:rust/clippy were blocked by UNRELATED stale shared-target/ cache (deleted sibling worktree readest-feat-android-rangefile-protocol path in fs plugin permission scan) — not my change; validated Rust via targeted plugin build. Related: android-open-with-intent-flow, android-nativefile-remotefile-io.