1 package android.hardware.cts.accessories 2 3 import android.app.Instrumentation 4 import android.app.UiAutomation 5 import android.os.Handler 6 import android.os.HandlerThread 7 import android.os.ParcelFileDescriptor 8 import android.util.JsonReader 9 import android.util.JsonToken 10 import android.util.Log 11 import androidx.test.platform.app.InstrumentationRegistry 12 import com.android.cts.input.HidResultData 13 import com.android.cts.input.InputJsonParser 14 import java.io.InputStream 15 import java.io.InputStreamReader 16 import java.io.IOException 17 import java.io.OutputStream 18 import java.io.UnsupportedEncodingException 19 import org.json.JSONArray 20 import org.json.JSONException 21 import org.json.JSONObject 22 23 abstract class HidCommand { 24 private var deviceId: Int? = null 25 private lateinit var parser: InputJsonParser 26 private lateinit var inputstream: InputStream 27 private lateinit var outputStream: OutputStream 28 private lateinit var reader: JsonReader 29 private lateinit var resultThread: Thread 30 private lateinit var handlerThread: HandlerThread 31 32 @JvmField 33 var handler: Handler? = null 34 private lateinit var instrumentation: Instrumentation processResultsnull35 protected abstract fun processResults(results: HidResultData) 36 37 private fun setupPipes() { 38 instrumentation = InstrumentationRegistry.getInstrumentation() 39 val ui: UiAutomation = instrumentation.getUiAutomation() 40 val pipes: Array<ParcelFileDescriptor> = ui.executeShellCommandRw(HidCommand.shellCommand) 41 inputstream = ParcelFileDescriptor.AutoCloseInputStream(pipes[0]) 42 outputStream = ParcelFileDescriptor.AutoCloseOutputStream(pipes[1]) 43 try { 44 reader = JsonReader(InputStreamReader(inputstream, "UTF-8")) 45 } catch (e: UnsupportedEncodingException) { 46 throw RuntimeException(e) 47 } 48 reader.setLenient(true) 49 } 50 51 /** 52 * Register a device using the provided json resource. 53 * 54 * @param deviceRegister The raw file that contains the json command 55 */ registerDevicenull56 fun registerDevice(registerCommand: Int) { 57 parser = InputJsonParser( 58 InstrumentationRegistry.getInstrumentation().getTargetContext() 59 ) 60 setupPipes() 61 deviceId = parser.readDeviceId(registerCommand) 62 writeCommands(parser.readRegisterCommand(registerCommand).toByteArray()) 63 handlerThread = HandlerThread("HidCommandThread") 64 handlerThread.start() 65 handler = Handler(handlerThread.getLooper()) 66 resultThread = Thread { 67 try { 68 while (reader.peek() !== JsonToken.END_DOCUMENT) { 69 processResults(readResults()) 70 } 71 } catch (ex: IOException) { 72 Log.w(TAG, "Exiting JSON Result reader. $ex") 73 } 74 } 75 resultThread.start() 76 } 77 setGetReportResponsenull78 protected fun setGetReportResponse(resultData: HidResultData) { 79 val result: StringBuilder = StringBuilder() 80 result.append("[") 81 for (reportData in resultData.reportData) { 82 result.append(String.format("0x%02x,", reportData)) 83 } 84 var report = result.toString() 85 report = report.substring(0, report.length - 1) + "]" 86 val json = JSONObject() 87 try { 88 json.put("command", "set_get_report_response") 89 json.put("id", deviceId) 90 json.put("report", JSONArray(report)) 91 } catch (e: JSONException) { 92 throw RuntimeException("Could not process HID report: $report") 93 } 94 writeCommands(json.toString().toByteArray()) 95 } 96 readResultsnull97 private fun readResults(): HidResultData { 98 val result: HidResultData = HidResultData() 99 try { 100 reader.beginObject() 101 while (reader.hasNext()) { 102 val fieldName: String = reader.nextName() 103 if (fieldName == "eventId") { 104 result.eventId = java.lang.Byte.decode(reader.nextString()) 105 } 106 if (fieldName == "deviceId") { 107 result.deviceId = Integer.decode(reader.nextString()) 108 } 109 if (fieldName == "reportType") { 110 result.reportType = java.lang.Byte.decode(reader.nextString()) 111 } 112 if (fieldName == "reportData") { 113 result.reportData = readData() 114 } 115 } 116 reader.endObject() 117 } catch (ex: IOException) { 118 Log.w(TAG, "Exiting JSON Result reader. $ex") 119 } 120 return result 121 } 122 sendSetReportReplynull123 protected fun sendSetReportReply(success: Boolean) { 124 val json = JSONObject() 125 try { 126 json.put("command", "send_set_report_reply") 127 json.put("id", deviceId) 128 json.put("success", success.toString()) 129 } catch (e: JSONException) { 130 throw RuntimeException("Could not process reply.") 131 } 132 writeCommands(json.toString().toByteArray()) 133 } 134 135 @Throws(IOException::class) readDatanull136 private fun readData(): ByteArray { 137 val dataList = ArrayList<Int>() 138 try { 139 reader.beginArray() 140 while (reader.hasNext()) { 141 dataList.add(Integer.decode(reader.nextString())) 142 } 143 reader.endArray() 144 } catch (e: IllegalStateException) { 145 reader.endArray() 146 throw IllegalStateException("Encountered malformed data.", e) 147 } catch (e: NumberFormatException) { 148 reader.endArray() 149 throw IllegalStateException("Encountered malformed data.", e) 150 } 151 val rawData = ByteArray(dataList.size) 152 for (i in dataList.indices) { 153 val d = dataList[i] 154 check(d and 0xFF == d) { "Invalid data, all values must be byte-sized" } 155 rawData[i] = d.toByte() 156 } 157 return rawData 158 } 159 sendHidReportnull160 protected fun sendHidReport(report: String) { 161 val json = JSONObject() 162 try { 163 json.put("command", "report") 164 json.put("id", deviceId) 165 json.put("report", JSONArray(report)) 166 } catch (e: JSONException) { 167 throw RuntimeException("Could not process HID report: $report") 168 } 169 writeCommands(json.toString().toByteArray()) 170 } 171 writeCommandsnull172 private fun writeCommands(bytes: ByteArray?) { 173 try { 174 outputStream.write(bytes) 175 outputStream.flush() 176 } catch (e: IOException) { 177 throw RuntimeException(e) 178 } 179 } 180 closeDevicenull181 fun closeDevice() { 182 inputstream.close() 183 outputStream.close() 184 } 185 186 companion object { 187 private const val TAG = "HidCommand" 188 189 // hid executable expects "-" argument to read from stdin instead of a file 190 val shellCommand = "hid -" 191 } 192 } 193