|
|
@@ -1,113 +1,144 @@
|
|
|
-package com.draco.ladb.utils
|
|
|
+package com.example.modifier.utils
|
|
|
|
|
|
-import android.annotation.SuppressLint
|
|
|
-import android.content.Context
|
|
|
-import android.provider.Settings
|
|
|
import android.util.Log
|
|
|
-import androidx.lifecycle.LiveData
|
|
|
-import androidx.lifecycle.MutableLiveData
|
|
|
+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
|
|
|
-import java.util.concurrent.TimeUnit
|
|
|
|
|
|
-class ADB(private val context: Context) {
|
|
|
+class ADB {
|
|
|
companion object {
|
|
|
const val TAG = "ADB"
|
|
|
- const val MAX_OUTPUT_BUFFER_SIZE = 1024 * 16
|
|
|
- const val OUTPUT_BUFFER_DELAY_MS = 100L
|
|
|
-
|
|
|
- @SuppressLint("StaticFieldLeak")
|
|
|
- @Volatile
|
|
|
- private var instance: ADB? = null
|
|
|
- fun getInstance(context: Context): ADB = instance ?: synchronized(this) {
|
|
|
- instance ?: ADB(context).also { instance = it }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- private val adbPath = "${context.applicationInfo.nativeLibraryDir}/libadb.so"
|
|
|
- private val scriptPath = "${context.getExternalFilesDir(null)}/script.sh"
|
|
|
-
|
|
|
- /**
|
|
|
- * Is the shell ready to handle commands?
|
|
|
- */
|
|
|
- private val _started = MutableLiveData(false)
|
|
|
- val started: LiveData<Boolean> = _started
|
|
|
-
|
|
|
- private var tryingToPair = false
|
|
|
-
|
|
|
- /**
|
|
|
- * Is the shell closed for any reason?
|
|
|
- */
|
|
|
- private val _closed = MutableLiveData(false)
|
|
|
- val closed: LiveData<Boolean> = _closed
|
|
|
-
|
|
|
-
|
|
|
- private fun isWirelessDebuggingEnabled() =
|
|
|
- Settings.Global.getInt(context.contentResolver, "adb_wifi_enabled", 0) == 1
|
|
|
-
|
|
|
- private fun isUSBDebuggingEnabled() =
|
|
|
- Settings.Global.getInt(context.contentResolver, Settings.Global.ADB_ENABLED, 0) == 1
|
|
|
-
|
|
|
-
|
|
|
- suspend fun connect(): Boolean {
|
|
|
-// adb(listOf("connect", "localhost:5555"))
|
|
|
- delay(5000)
|
|
|
-
|
|
|
- adb(listOf("devices"))
|
|
|
-
|
|
|
- val shell2 = adb(listOf("shell", "ls"))
|
|
|
- return shell2.exitValue() == 0
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Send a raw ADB command
|
|
|
- */
|
|
|
- suspend fun adb(command: List<String>): Process {
|
|
|
- val commandList = command.toMutableList().also {
|
|
|
- it.add(0, adbPath)
|
|
|
+ private 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
|
|
|
+ }
|
|
|
}
|
|
|
- return shell(commandList)
|
|
|
- }
|
|
|
|
|
|
- /**
|
|
|
- * Send a raw shell command
|
|
|
- */
|
|
|
- private suspend fun shell(command: List<String>): Process {
|
|
|
- val process = withContext(Dispatchers.IO) {
|
|
|
- ProcessBuilder(command)
|
|
|
- .directory(context.filesDir)
|
|
|
- .apply {
|
|
|
- redirectErrorStream(true)
|
|
|
- environment().apply {
|
|
|
- put("HOME", context.filesDir.path)
|
|
|
- put("TMPDIR", context.cacheDir.path)
|
|
|
+ /**
|
|
|
+ * 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")
|
|
|
}
|
|
|
}
|
|
|
- .start()
|
|
|
- }
|
|
|
- coroutineScope {
|
|
|
- launch {
|
|
|
- process.inputStream.bufferedReader().useLines { line ->
|
|
|
- line.forEach {
|
|
|
- Log.i(TAG, "ADB out: $it")
|
|
|
+ Log.i(TAG, "ADB command: ${fullCommand.joinToString(" ")}".replace(adbPath, "adb"))
|
|
|
+ var output = ""
|
|
|
+ var error = ""
|
|
|
+ val process = withContext(Dispatchers.IO) {
|
|
|
+ ProcessBuilder(fullCommand)
|
|
|
+ .directory(homeDir)
|
|
|
+ .apply {
|
|
|
+// redirectErrorStream(true)
|
|
|
+ environment().apply {
|
|
|
+ put("HOME", homeDir.path)
|
|
|
+ put("TMPDIR", cacheDir.path)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .start()
|
|
|
+ }
|
|
|
+ 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)
|
|
|
}
|
|
|
- launch {
|
|
|
- process.errorStream.bufferedReader().useLines { line ->
|
|
|
- line.forEach {
|
|
|
- Log.e(TAG, "ADB err: $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) {
|
|
|
+ ProcessBuilder(listOf(adbPath, "-s", "localhost:5555", "shell"))
|
|
|
+ .directory(homeDir)
|
|
|
+ .apply {
|
|
|
+// redirectErrorStream(true)
|
|
|
+ environment().apply {
|
|
|
+ put("HOME", homeDir.path)
|
|
|
+ put("TMPDIR", cacheDir.path)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .start()
|
|
|
+ }
|
|
|
+ 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()
|
|
|
}
|
|
|
- withContext(Dispatchers.IO) {
|
|
|
- process.waitFor()
|
|
|
- }
|
|
|
- return process
|
|
|
}
|
|
|
}
|