Wireless Debugging pairing workflow for Android apps.
ADBeesh is a library that allows an Android app to establish a local ADB connection to the device via Wireless Debugging, providing a bridge for privileged command execution as the shell (2000) user. ADBeesh does the heavy lifting of ADB key management and UI interaction to let your app simply request a pairing and guide the user to perform the pairing workflow.
ADBeesh is simply a convenience layer, a UI workflow that simplifies ADB pairing for power-user apps. It exists to simplify a workflow every developer had to implement to get a direct ADB shell.
Shizuku is a frontend app whose ADB mode allows running ADB privileged code. To do so, the user has to pair the app with the device. ADBeesh implements exactly this step in a generic fashion, allowing developers to make Shizuku-like apps themselves.
It's not different from a regular ADB pairing workflow. While a foreground service keeps the app alive, the user is directed to Wireless Debugging workflow, initiate a pairing and submit the code through a notification.
The workflow is customizable, meaning the developer can tailor parts of it to their needs, for example get the code in another way, customize instructions, control the ADB certificate subject, replace notifications with toast pop-ups and more. While many things are customizable, some are not. The library is opinionated and scoped to do exactly one thing, meaning you can't launch a space shuttle with it (and you shouldn't). If a feature you need is missing, you're welcome to open an issue/PR.
Warning
Proper security audit of the library hasn't been conducted yet.
Pairing keys are currently stored in encrypted shared preferences (androidx.security:security-crypto)
which may change in the future. The security of ADB implementation depends on the upstream Kadb project.
Use at your own risk.
Public API documentation is available here
dependencies {
implementation("com.indidevs.android:adbeesh:LATEST_VERSION")
}Add this to your pom.xml dependencies block:
<dependency>
<groupId>com.indidevs.android</groupId>
<artifactId>adbeesh</artifactId>
<version>LATEST_VERSION</version>
</dependency>Load the maven package rules in MODULE.bazel and specify the artifact:
bazel_dep(name = "rules_jvm_external", version = "X.Y")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
maven.install(
name = "maven",
artifacts = [
"com.indidevs.android:adbeesh:LATEST_VERSION",
],
)
use_repo(maven, "maven")Specify dependencies wherever required:
deps = ["@maven//:com_indidevs_android_adbeesh"],- Android 11+ (API 30): Required for Wireless Debugging pairing protocol.
- Permissions: ADBeesh requires several permissions (declared in manifest) to handle networking and foreground services.
INTERNET: For ADB TLS connection.FOREGROUND_SERVICE: To keep pairing alive.FOREGROUND_SERVICE_CONNECTED_DEVICE: For Android 14+ workflow.POST_NOTIFICATIONS: For Android 13+ pairing notification (request at runtime if usingNotificationCodeProvider).CHANGE_WIFI_MULTICAST_STATE: For reliable mDNS discovery.
ADBeesh uses a foreground service to manage the pairing notification.
<service
android:name="com.indidevs.android.adbeesh.pairing.AdbPairingService"
android:foregroundServiceType="connectedDevice"
android:exported="false" />The core ADB identity and handshake parameters are configured via AdbSessionConfig.
val sessionConfig = AdbSessionConfig.Builder()
.identityName("MyApp")
.build()ADBeesh uses a foreground service to manage the pairing workflow. You can customize the visual appearance using the NotificationFlowConfig DSL.
val flowConfig = NotificationFlowConfig.Builder()
// Configure the Android Notification Channel
.channel(id = "my_app_channel", name = "MyApp Pairing")
// Global Branding: Applies to all stages by default
.common {
title = "ADB Pairing MyApp"
smallIconRes = R.drawable.ic_adb
settingsButtonText = "OPEN SETTINGS"
}
// Overrides: Tweak specific parts of the flow
.searching {
text = "Searching for Wireless Debugging..."
}
.awaitingCode { update ->
text = "Device found on port ${update.port}. Enter pairing code:"
inputLabel = "Wi-Fi pairing code"
}
.success {
title = "Connected!"
text = "Pairing successful."
}
.build()Tip
See docs:
- NotificationFlowConfig: Visual settings for the pairing notification flow.
- NotificationConfig: Individual notification states (titles, text, and buttons).
A pairing handle represents a persistent identity. Pairings are persisted by an id (storage key). If the device is not yet paired, trigger the AdbPairingWorkflow.
You can provide your own UI for entering the 6-digit code by implementing AdbPairingCodeProvider or handle lifecycle events via AdbPairingCallback.
val pairing = AdbPairing.get(context, id = "main", config = sessionConfig)
val workflow = AdbPairingWorkflow.Builder()
.withCodeProvider(NotificationCodeProvider(context, flowConfig))
.addCallback(NotificationCallback(context, flowConfig))
// Optional: Add your own custom listeners
.addCallback(object : AdbPairingCallback {
override fun onPairingStarted(sessionId: String) {
println("Started searching for device")
}
override fun onPortFound(sessionId: String, port: Int) {
println("Found candidate port: $port")
}
})
.build()
// In a ViewModel or LifecycleOwner
lifecycleScope.launch {
try {
pairing.ensurePaired(workflow)
} catch (e: AdbException) { }
}Tip
See docs:
- AdbPairingWorkflow: Orchestrates the pairing process.
- AdbPairingCodeProvider: Interface for requesting the pairing code.
- AdbPairingCallback: Lifecycle events for the pairing process.
Once paired, you can open a session and execute commands.
The shell execution returns an AdbShellResult.
and any library errors (timeouts, auth failures) are thrown as an AdbException.
lifecycleScope.launch {
try {
pairing.openSession(timeoutMs = 5000).use { session ->
if (session.isAlive()) {
val result = session.shell("pm grant com.myapp android.permission.WRITE_SECURE_SETTINGS")
if (result.isSuccess) {
println("Output: ${result.output}")
}
// Access underlying Kadb instance for advanced features (push/pull/install)
val kadb = session.kadb
kadb.install(apkFile)
}
}
} catch (e: AdbException) { }
}Tip
See docs:
- AdbShellResult: The results of shell commands, including exit codes and output.
- AdbException: Library-specific exceptions.
The pairing lifecycle can be monitored via the status Flow, which emits AdbPairingStatus values.
lifecycleScope.launch {
pairing.status.collect { status ->
when(status) {
AdbPairingStatus.IDLE -> println("Ready to start")
AdbPairingStatus.DISCOVERING -> println("Searching")
AdbPairingStatus.AWAITING_CODE -> println("Enter code in notification")
AdbPairingStatus.PAIRING -> println("Handshaking")
AdbPairingStatus.PAIRED -> println("Ready!")
AdbPairingStatus.ERROR -> println("Something went wrong")
}
}
}Tip
See docs: AdbPairingStatus: Enum representing the possible states of a pairing handle.
To forget the identity and delete stored keys:
pairing.unpair()You can implement your own UI for entering the pairing code by implementing AdbPairingCodeProvider
and handle lifecycle events via AdbPairingCallback.
class MyUiProvider : AdbPairingCodeProvider {
override suspend fun provideCode(sessionId: String, port: Int): String? {
// Show your own dialog/activity and return the 6-digit code
return getCodeFromUserWithMyUI()
}
}
class MyUiCallback : AdbPairingCallback {
override fun onPairingStarted(sessionId: String) {
// Called when discovery starts
}
override fun onPortFound(sessionId: String, port: Int) {
// Called when a candidate port is found
}
override fun onPairingHandshakeStarted(sessionId: String) {
// Called when the cryptographic handshake starts
}
override fun onPairingSuccess(sessionId: String) {
// Called when pairing succeeds
}
override fun onPairingError(sessionId: String, error: String) {
// Called when pairing fails
}
}val workflow = AdbPairingWorkflow.Builder()
.withCodeProvider(MyUiProvider())
.addCallback(MyUiCallback())
.build()
lifecycleScope.launch {
pairing.ensurePaired(workflow)
}ADBeesh is licensed under the MIT License.
This project uses components licensed under the Apache License 2.0 (including a fork of Kadb). See the NOTICE and LICENSE-APACHE files for full attribution and license details.