Skip to content

Internalize Shizuku privileged layer to remove external app dependency #46

@Suprhimp

Description

@Suprhimp

Problem

Castla currently depends on users installing and maintaining a separate app — Shizuku — as its privileged-access layer. This creates a real onboarding burden:

  • Users must install a second APK
  • Users must enable Wireless Debugging in Developer Options
  • Users must (re)start Shizuku after each reboot unless set up carefully
  • Shizuku's setup flow is itself non-trivial (the project ships shizuku-install-guide.md for this reason)
  • Any Shizuku version drift, crash, or misconfiguration breaks Castla's touch injection, virtual display, and hotspot control

For a "plug-and-mirror to Tesla" product, this is the single biggest friction point in the first-run experience.

Current Shizuku surface area used by Castla

We only touch a narrow slice of the Shizuku API:

  • Shizuku.pingBinder() / isPreV11() — liveness + version checks
  • Shizuku.checkSelfPermission() / requestPermission() — permission gating
  • Shizuku.bindUserService() / unbindUserService() — IPC to our own PrivilegedService (defined in this repo)
  • Shizuku.addBinderReceivedListener* / addBinderDeadListener / addRequestPermissionResultListener — lifecycle signals

Our actual privileged work is already 100% in-repo: app/src/main/aidl/com/castla/mirror/shizuku/IPrivilegedService.aidl defines every method we call, and PrivilegedService.kt implements them. Shizuku is effectively acting as a launcher + binder broker for code we already own.

Proposal

Replace the external Shizuku dependency with an in-repo privileged host that provides the same primitive:

"Run a Binder-exposing app_process server under the shell UID, started via ADB (wireless debugging or USB), and have the app bind to it."

This is precisely what Shizuku does internally; we just copy that pattern at a scale that fits our actual needs. The work roughly breaks down into:

  1. Privileged server entry point — an app_process-executable Kotlin/Java class that lives inside our APK, reads a Binder interface, and publishes it (either via ServiceManager.addService or a socket handshake with the app process).
  2. Launcher script — a shell one-liner / tiny helper that users can paste into adb shell (or into their phone's on-device wireless-debugging terminal app) to kick the server off. Equivalent to Shizuku's sh /storage/.../start.sh.
  3. App-side bootstrap — replace Shizuku.bindUserService with our own connect-to-Binder call. PrivilegedService.kt can remain almost unchanged.
  4. Teardown & auto-restart — detect dead binder, surface clear UI state, offer a one-tap re-pair flow.
  5. Keep a "use existing Shizuku" fallback behind a flag for a release or two, so current users aren't forced to re-pair on day one.

Benefits

  • One-APK install. No external app.
  • Tighter UX. Setup wizard becomes linear: plug in → wireless debug → paste one command → done.
  • Version control. We pin the privileged-server version to the APK version. No more "works on Shizuku 13.x, broken on 14.x" class of bugs.
  • Shipping surface we can harden. Crash handling, logs, watchdog, diagnostics all live where our other code does.
  • Clean OSS story. The Apache-2.0 app becomes self-contained; NOTICE still credits Shizuku for inspiration, but runtime dependency drops to zero.

Risks / open questions

  • Licensing: reimplementing in the style of Shizuku is fine; copying non-trivial code would bind us to Apache-2.0 attribution but isn't otherwise a problem since we just relicensed to Apache-2.0. Still, we should write our own rather than copy.
  • Pairing UX via ADB pairing code is Android-version-sensitive (pre-11 vs 11+ vs 13+). Needs matrix testing.
  • Samsung / MIUI quirks around wireless debugging — Shizuku absorbs a lot of these today; we'll inherit the reports.
  • Server-side capabilities scope: exactly which AOSP internal APIs do we reach for (DisplayControl, InputManager, WifiManager tethering)? Document the blast radius.
  • Security posture: the Binder we publish is reachable by any app with the right token. We need the same "grant once per installation" gate Shizuku provides.

Non-goals

  • Root support. Keep ADB-only for now to match current Castla UX.
  • Cross-app reuse. We explicitly don't want to become the next Shizuku; the privileged server is single-tenant for Castla.

Next step

Spike on just item (1) + (2): can we get a minimal app_process-launched Binder service running and PrivilegedService.kt bound to it, behind a build flag? If yes, the rest is incremental.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions