package com.example.modifier.utils import android.util.Log import com.example.modifier.model.AdbResult import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class ADB { companion object { const val TAG = "ADB" val adbPath by lazy { "${getContext().applicationInfo.nativeLibraryDir}/libadb.so" } private val homeDir by lazy { getContext().filesDir } private val cacheDir by lazy { getContext().cacheDir } suspend fun connect(): Boolean { return run ls@{ repeat(1000) { adb("connect", "localhost:5555") delay(5000) adb("devices") if (adb("shell", "echo", "hello").exitCode == 0) { return@ls true } delay(3000) } return@ls false } } /** * Send a raw ADB command */ suspend fun adb(vararg command: String): AdbResult { runCatching { val fullCommand = command.toMutableList().also { it.add(0, adbPath) if (command.firstOrNull() == "shell") { it.add(1, "-s") it.add(2, "localhost:5555") } } Log.i(TAG, "ADB command: ${fullCommand.joinToString(" ")}".replace(adbPath, "adb")) var output = "" var error = "" val process = withContext(Dispatchers.IO) { createProcess(*fullCommand.toTypedArray()) } coroutineScope { launch { process.inputStream.bufferedReader().useLines { line -> line.forEach { output += it + "\n" Log.i(TAG, "ADB out: $it") } } } launch { process.errorStream.bufferedReader().useLines { line -> line.forEach { error += it + "\n" Log.e(TAG, "ADB err: $it") } } } } withContext(Dispatchers.IO) { process.waitFor() } return AdbResult(exitCode = process.exitValue()) }.onFailure { Log.wtf(TAG, "Error running ADB command: ${it.message}", it) } return AdbResult() } /** * Send a raw ADB command */ suspend fun adbShell(vararg commands: String): AdbResult { Log.i(TAG, "ADB command: ${commands.joinToString("\n")}") runCatching { var output = "" var error = "" val process = withContext(Dispatchers.IO) { createProcess(adbPath, "-s", "localhost:5555", "shell") } process.outputStream.bufferedWriter().use { writer -> commands.forEach { command -> writer.write(command) writer.newLine() writer.flush() } } coroutineScope { launch { process.inputStream.bufferedReader().useLines { line -> line.forEach { output += it + "\n" Log.i(TAG, "ADB out: $it") } } } launch { process.errorStream.bufferedReader().useLines { line -> line.forEach { error += it + "\n" Log.e(TAG, "ADB err: $it") } } } } withContext(Dispatchers.IO) { process.waitFor() } Log.i(TAG, "ADB exit code: ${process.exitValue()}") return AdbResult(output = output, error = error, exitCode = process.exitValue()) }.onFailure { Log.wtf(TAG, "Error running ADB command: ${it.message}", it) } return AdbResult() } fun createProcess(vararg command: String): Process { return ProcessBuilder(*command) .directory(homeDir) .apply { // redirectErrorStream(true) environment().apply { put("HOME", homeDir.path) put("TMPDIR", cacheDir.path) } }.start() } } }