AdbShell.kt 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. @file:JvmName("AdbShell")
  2. package eu.mjdev.dadb.helpers
  3. import eu.mjdev.dadb.helpers.AdbShellPacket.*
  4. import java.io.IOException
  5. const val ID_STDIN = 0
  6. const val ID_STDOUT = 1
  7. const val ID_STDERR = 2
  8. const val ID_EXIT = 3
  9. const val ID_CLOSE_STDIN = 3
  10. class AdbShellStream(
  11. private val stream: AdbStream
  12. ) : AutoCloseable {
  13. @Throws(IOException::class)
  14. fun readAll(): AdbShellResponse {
  15. val output = StringBuilder()
  16. val errorOutput = StringBuilder()
  17. while (true) {
  18. when (val packet = read()) {
  19. is Exit -> {
  20. val exitCode = packet.payload[0].toInt()
  21. return AdbShellResponse(output.toString(), errorOutput.toString(), exitCode)
  22. }
  23. is StdOut -> {
  24. output.append(String(packet.payload))
  25. }
  26. is StdError -> {
  27. errorOutput.append(String(packet.payload))
  28. }
  29. }
  30. }
  31. }
  32. @Throws(IOException::class)
  33. fun read(): AdbShellPacket {
  34. stream.source.apply {
  35. val id = checkId(readByte().toInt())
  36. val length = checkLength(id, readIntLe())
  37. val payload = readByteArray(length.toLong())
  38. return when (id) {
  39. ID_STDOUT -> StdOut(payload)
  40. ID_STDERR -> StdError(payload)
  41. ID_EXIT -> Exit(payload)
  42. else -> throw IllegalArgumentException("Invalid shell packet id: $id")
  43. }
  44. }
  45. }
  46. @Throws(IOException::class)
  47. fun write(string: String) {
  48. write(ID_STDIN, string.toByteArray())
  49. }
  50. @Throws(IOException::class)
  51. fun write(id: Int, payload: ByteArray? = null) {
  52. stream.sink.apply {
  53. writeByte(id)
  54. writeIntLe(payload?.size ?: 0)
  55. if (payload != null) write(payload)
  56. flush()
  57. }
  58. }
  59. override fun close() {
  60. stream.close()
  61. }
  62. private fun checkId(id: Int): Int {
  63. check(id == ID_STDOUT || id == ID_STDERR || id == ID_EXIT) {
  64. "Invalid shell packet id: $id"
  65. }
  66. return id
  67. }
  68. private fun checkLength(id: Int, length: Int): Int {
  69. check(length >= 0) { "Shell packet length must be >= 0: $length" }
  70. check(id != ID_EXIT || length == 1) { "Shell exit packet does not have payload length == 1: $length" }
  71. return length
  72. }
  73. }
  74. sealed class AdbShellPacket(
  75. open val payload: ByteArray
  76. ) {
  77. abstract val id: Int
  78. class StdOut(override val payload: ByteArray) : AdbShellPacket(payload) {
  79. override val id: Int = ID_STDOUT
  80. override fun toString() = "STDOUT: ${String(payload)}"
  81. }
  82. class StdError(override val payload: ByteArray) : AdbShellPacket(payload) {
  83. override val id: Int = ID_STDERR
  84. override fun toString() = "STDERR: ${String(payload)}"
  85. }
  86. class Exit(override val payload: ByteArray) : AdbShellPacket(payload) {
  87. override val id: Int = ID_EXIT
  88. override fun toString() = "EXIT: ${payload[0]}"
  89. }
  90. }
  91. class AdbShellResponse(
  92. val output: String,
  93. val errorOutput: String,
  94. val exitCode: Int
  95. ) {
  96. val allOutput: String by lazy { "$output$errorOutput" }
  97. override fun toString() = "Shell response ($exitCode):\n$allOutput"
  98. }