• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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