RootShellBackend
ShellBackend backed by libsu — runs commands as the root UID. Strictly more powerful than Shizuku's ShizukuShellBackend: writes to /system, freezes apps via pm hide, reads /data/data/<pkg>, etc.
One shell per call. Each exec / execBinary builds a dedicated su shell via Shell.Builder.create().build() and closes it afterwards, instead of using libsu's process-global main shell. A command that reads stdin, cds, exports variables, execs or exits therefore can't poison later calls (or the host's own libsu usage). The command's stdin is additionally redirected from /dev/null. Building a shell costs one su round-trip (typically a few ms; some superuser managers show a toast / log entry per session).
Bounds. Each call is limited to execTimeoutMs and to maxOutputBytes of captured stdout / stderr each. On timeout or caller cancellation the dedicated shell is closed (killing the su process and its child); a timeout surfaces as ShellException.SpawnFailed. When a stream exceeds its cap the shell is killed and the captured prefix is returned with ShellResult.outputTruncated set and exitCode = -1.
Output model. libsu's Shell.Result.out is List<String> (UTF-8 line-decoded). exec joins those lines back into a byte stream via UTF-8 re-encoding which is lossy for binary stdout (the PNG signature 89 50 4E 47 ... is invalid UTF-8, so the round-trip mangles it). Tools whose stdout is raw bytes — screencap -p, anything streaming non-UTF-8 — must call execBinary, which uses a temp-file roundtrip via SuFileInputStream to read raw bytes. stderr is collected separately (Job.to(out, err)), so ShellResult.stderr is populated.
Host responsibility — do not override libsu's default builder to a non-root config. requestAccess calls Shell.getShell which uses whatever default builder is configured. If your host has called Shell.setDefaultBuilder(...) with non-root flags before any droid-mcp code runs, the prompt will never fire and root tools will silently fail with shell_unavailable. libsu's untouched default prefers root, which is the configuration droid-mcp expects. (The per-call shells use a fresh default Shell.Builder, unaffected by the host's.)
Command parsing. libsu writes each command to the stdin of the shell process — it is NOT a sh -c <string> wrap. The shell still parses each line per sh syntax, so argv quoting via ShellUtils.escapedString is applied to every argument.
Parameters
wall-clock limit per call. The core ToolRegistry applies its own (longer) per-tool timeout on top.
per-stream capture cap (approximate: counted as UTF-16 chars + 1 per line, as libsu hands us decoded lines).
stdout byte cap for execBinary.
Functions
Binary-safe path: writes stdout to a tempfile under /data/local/tmp (created under umask 077, so it is root-only 0600 from the start), then reads it raw via SuFileInputStream, capped at maxBinaryOutputBytes. Used by capture_screen_quiet for PNG bytes.