app: accurate Shizuku handoff — name the steps, add developer-options shortcut

Verified against Shizuku 13.6's manifest (pulled APK, aapt2 xmltree): its
wireless-debugging entry points (AdbPairingTutorialActivity,
AdbPairingService, StarterActivity) have no intent filters, so they are
not exported and cannot be launched externally; MainActivity answers only
MAIN/LAUNCHER with no deep link. Starting wireless debugging from another
app is therefore not possible, which is why the handoff lands on the
root-start screen.

Instead the hint now names the exact steps inside Shizuku ("Pairing", then
"Start"), and a second tap opens Developer options — that action IS public
and exported, and Wireless debugging has to be on before Shizuku's
wireless start works.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mrambossek
2026-08-01 10:03:03 +02:00
co-authored by Claude Opus 5
parent 1d9e2063bf
commit c75a9f5eb7
3 changed files with 54 additions and 6 deletions
@@ -65,6 +65,13 @@ class MainActivity : ComponentActivity() {
state = vm.state,
onRun = { vm.run() },
onCancel = vm::cancel,
onDeveloperOptions = {
runCatching {
startActivity(
app.echo_lot.shizuku.ShizukuAvailability.developerOptionsIntent()
)
}
},
onShizukuAction = {
// Shizuku can only be started from its own app (the pairing flow lives
// there), so send the user straight to it; if it is already running we
@@ -114,6 +121,7 @@ private fun EcholotScreen(
onRun: () -> Unit,
onCancel: () -> Unit,
onShizukuAction: () -> Unit,
onDeveloperOptions: () -> Unit,
onExport: (MeasurementDocument) -> Unit,
) {
Column(
@@ -150,6 +158,17 @@ private fun EcholotScreen(
Text(it, fontSize = 11.sp, fontWeight = FontWeight.SemiBold,
color = Color(0xFFFFB454))
}
// Wireless debugging must be ON before Shizuku's wireless start works, and
// that screen (unlike Shizuku's pairing activity) is publicly launchable.
if (state.shizukuState ==
app.echo_lot.shizuku.ShizukuAvailability.State.INSTALLED_NOT_RUNNING
) {
Text(
"Wireless debugging settings →",
Modifier.padding(top = 6.dp).clickable { onDeveloperOptions() },
fontSize = 11.sp, color = Color(0xFF35E0C4),
)
}
}
}
}
@@ -59,10 +59,14 @@ object ShizukuAvailability {
}
/**
* Intent that opens the Shizuku manager, where the user starts the service ("Start with
* wireless debugging" / root). A third-party app cannot start Shizuku itself — the pairing
* flow lives inside that app — so the best we can do is take the user straight there.
* Null when Shizuku isn't installed or exposes no launcher activity.
* Intent that opens the Shizuku manager.
*
* Only `MainActivity` is reachable: verified against Shizuku 13.6's own manifest, the
* wireless-debugging entry points — `moe.shizuku.manager.adb.AdbPairingTutorialActivity`,
* `moe.shizuku.manager.adb.AdbPairingService` and `moe.shizuku.manager.starter.StarterActivity`
* — declare no intent filters, so they are NOT exported and a third-party app cannot launch
* them (MainActivity itself only answers MAIN/LAUNCHER, no deep link). Landing on the main
* screen and telling the user which button to press is therefore the best available handoff.
*/
fun launchIntent(context: Context): android.content.Intent? =
context.packageManager.getLaunchIntentForPackage(SHIZUKU_PACKAGE)
@@ -73,13 +77,26 @@ object ShizukuAvailability {
runCatching { Shizuku.requestPermission(requestCode) }
}
/** What tapping the notice should do, so the UI can label it honestly. */
/**
* What tapping the notice does. For the stopped case the label names the exact steps inside
* Shizuku, because we can only hand the user to its main screen (see [launchIntent]).
*/
fun actionHint(s: State): String? = when (s) {
State.INSTALLED_NOT_RUNNING -> "Tap to open Shizuku and start it"
State.INSTALLED_NOT_RUNNING ->
"Tap to open Shizuku → \"Pairing\" to pair, then \"Start\" (wireless debugging)"
State.NEEDS_PERMISSION -> "Tap to grant permission"
else -> null
}
/**
* Developer-options screen, where Wireless debugging is enabled — the prerequisite Shizuku's
* wireless start depends on. This action IS public and exported, unlike Shizuku's own
* pairing screen.
*/
fun developerOptionsIntent(): android.content.Intent =
android.content.Intent(android.provider.Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS)
.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
/**
* Reports the state now and on every binder transition. Returns a function that removes the
* listeners again (call it from onCleared).