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:
- 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).
- 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.
- App-side bootstrap — replace
Shizuku.bindUserService with our own connect-to-Binder call. PrivilegedService.kt can remain almost unchanged.
- Teardown & auto-restart — detect dead binder, surface clear UI state, offer a one-tap re-pair flow.
- 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.
Problem
Castla currently depends on users installing and maintaining a separate app — Shizuku — as its privileged-access layer. This creates a real onboarding burden:
shizuku-install-guide.mdfor this reason)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 checksShizuku.checkSelfPermission()/requestPermission()— permission gatingShizuku.bindUserService()/unbindUserService()— IPC to our ownPrivilegedService(defined in this repo)Shizuku.addBinderReceivedListener*/addBinderDeadListener/addRequestPermissionResultListener— lifecycle signalsOur actual privileged work is already 100% in-repo:
app/src/main/aidl/com/castla/mirror/shizuku/IPrivilegedService.aidldefines every method we call, andPrivilegedService.ktimplements 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:
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:
app_process-executable Kotlin/Java class that lives inside our APK, reads a Binder interface, and publishes it (either viaServiceManager.addServiceor a socket handshake with the app process).adb shell(or into their phone's on-device wireless-debugging terminal app) to kick the server off. Equivalent to Shizuku'ssh /storage/.../start.sh.Shizuku.bindUserServicewith our own connect-to-Binder call.PrivilegedService.ktcan remain almost unchanged.Benefits
Risks / open questions
Non-goals
Next step
Spike on just item (1) + (2): can we get a minimal
app_process-launched Binder service running andPrivilegedService.ktbound to it, behind a build flag? If yes, the rest is incremental.