Last active
November 2, 2019 04:13
-
-
Save SaschaZ/4deeadbf72318a5451be1c9fa99940d5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env kscript | |
//DEPS org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0 | |
import java.io.File | |
import java.util.concurrent.TimeUnit | |
import java.io.IOException | |
import kotlinx.coroutines.* | |
import kotlinx.coroutines.sync.Mutex | |
import kotlinx.coroutines.sync.withLock | |
import kotlin.coroutines.CoroutineContext | |
import kotlinx.coroutines.CoroutineScope | |
import kotlin.coroutines.resume | |
import kotlin.coroutines.suspendCoroutine | |
import kotlinx.coroutines.Dispatchers | |
object Bash { | |
suspend fun runCommand( | |
command: String, | |
workingDir: File = File("."), | |
timeoutMinutes: Long = Long.MAX_VALUE shl 2 | |
): String? { | |
try { | |
val parts = command.split("\\s".toRegex()) | |
val proc = ProcessBuilder(*parts.toTypedArray()) | |
.directory(workingDir) | |
.redirectOutput(ProcessBuilder.Redirect.PIPE) | |
.redirectError(ProcessBuilder.Redirect.PIPE) | |
return suspendCoroutine<Process> { | |
it.resume<Process>(proc.start().also { it.waitFor(timeoutMinutes, TimeUnit.MINUTES) }) | |
}.inputStream.bufferedReader().readText() | |
} catch (e: IOException) { | |
e.printStackTrace() | |
return null | |
} | |
} | |
} | |
object AndroidDeviceIdProvider : CoroutineScope { | |
override val coroutineContext = Dispatchers.IO + Job() | |
suspend fun provide( | |
pollingInterval: Long = 5000L, | |
listener: suspend (deviceId: String, add: Boolean) -> Unit | |
) { | |
val reportedDevices = ArrayList<String>() | |
while (isActive) { | |
readDeviceIds()?.also { latestDevices -> | |
val devicesToAdd = latestDevices.filter { !reportedDevices.contains(it) } | |
devicesToAdd.forEach { | |
println("new device id $it") | |
listener(it, true) | |
reportedDevices.add(it) | |
} | |
val devicesToRemove = reportedDevices.filter { !latestDevices.contains(it) } | |
devicesToRemove.forEach { | |
println("removed device id $it") | |
listener(it, false) | |
reportedDevices.remove(it) | |
} | |
} | |
delay(pollingInterval) | |
} | |
} | |
private suspend fun readDeviceIds(): List<String>? = | |
Bash.runCommand("adb devices")?.let { devicesOutput -> | |
if (devicesOutput.isBlank()) null | |
else { | |
val nonBlankLines = devicesOutput.split("\n").filter { it.isNotBlank() } | |
val firstWordsOfLines = nonBlankLines.map { it.split("\t").first() }.filter { it.isNotBlank() } | |
(1..firstWordsOfLines.lastIndex).map { firstWordsOfLines[it] } | |
} | |
} ?: null | |
} | |
object ScrCpyStarter : CoroutineScope { | |
override val coroutineContext = Dispatchers.IO + Job() | |
private var recentStart = 0L | |
private val startMutex = Mutex() | |
private val idJobMap = HashMap<String, Job>() | |
suspend fun runScrCpy(device: String) { | |
idJobMap[device] = launch(Dispatchers.IO) { | |
while (isActive) { | |
scrCpy(device) | |
} | |
} | |
} | |
fun cancelSrcCpy(device: String) = idJobMap[device]?.cancel() | |
private suspend fun scrCpy(device: String): String? { | |
startMutex.withLock { | |
val timeDiff = System.currentTimeMillis() - recentStart | |
if (timeDiff < 5000) delay(timeDiff.toLong()) | |
recentStart = System.currentTimeMillis() | |
} | |
return Bash.runCommand("scrcpy -s $device") | |
} | |
} | |
runBlocking { | |
AndroidDeviceIdProvider.provide { id, add -> ScrCpyStarter.run { if (add) runScrCpy(id) else cancelSrcCpy(id) } } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment