1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.commands.hid;
18 
19 import android.util.JsonReader;
20 import android.util.JsonToken;
21 import android.util.Log;
22 import android.util.SparseArray;
23 
24 import java.io.IOException;
25 import java.io.InputStreamReader;
26 import java.nio.ByteBuffer;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.HashMap;
30 import java.util.Map;
31 
32 public class Event {
33     private static final String TAG = "HidEvent";
34 
35     public static final String COMMAND_REGISTER = "register";
36     public static final String COMMAND_DELAY = "delay";
37     public static final String COMMAND_REPORT = "report";
38 
39     // These constants come from "include/uapi/linux/input.h" in the kernel
40     enum Bus {
41         USB(0x03), BLUETOOTH(0x05);
42 
Bus(int value)43         Bus(int value) {
44             mValue = value;
45         }
46 
getValue()47         int getValue() {
48             return mValue;
49         }
50 
51         private int mValue;
52     }
53 
54     private int mId;
55     private String mCommand;
56     private String mName;
57     private byte[] mDescriptor;
58     private int mVid;
59     private int mPid;
60     private Bus mBus;
61     private byte[] mReport;
62     private SparseArray<byte[]> mFeatureReports;
63     private Map<ByteBuffer, byte[]> mOutputs;
64     private int mDuration;
65 
getId()66     public int getId() {
67         return mId;
68     }
69 
getCommand()70     public String getCommand() {
71         return mCommand;
72     }
73 
getName()74     public String getName() {
75         return mName;
76     }
77 
getDescriptor()78     public byte[] getDescriptor() {
79         return mDescriptor;
80     }
81 
getVendorId()82     public int getVendorId() {
83         return mVid;
84     }
85 
getProductId()86     public int getProductId() {
87         return mPid;
88     }
89 
getBus()90     public int getBus() {
91         return mBus.getValue();
92     }
93 
getReport()94     public byte[] getReport() {
95         return mReport;
96     }
97 
getFeatureReports()98     public SparseArray<byte[]> getFeatureReports() {
99         return mFeatureReports;
100     }
101 
getOutputs()102     public Map<ByteBuffer, byte[]> getOutputs() {
103         return mOutputs;
104     }
105 
getDuration()106     public int getDuration() {
107         return mDuration;
108     }
109 
toString()110     public String toString() {
111         return "Event{id=" + mId
112             + ", command=" + String.valueOf(mCommand)
113             + ", name=" + String.valueOf(mName)
114             + ", descriptor=" + Arrays.toString(mDescriptor)
115             + ", vid=" + mVid
116             + ", pid=" + mPid
117             + ", bus=" + mBus
118             + ", report=" + Arrays.toString(mReport)
119             + ", feature_reports=" + mFeatureReports.toString()
120             + ", outputs=" + mOutputs.toString()
121             + ", duration=" + mDuration
122             + "}";
123     }
124 
125     private static class Builder {
126         private Event mEvent;
127 
Builder()128         public Builder() {
129             mEvent = new Event();
130         }
131 
setId(int id)132         public void setId(int id) {
133             mEvent.mId = id;
134         }
135 
setCommand(String command)136         private void setCommand(String command) {
137             mEvent.mCommand = command;
138         }
139 
setName(String name)140         public void setName(String name) {
141             mEvent.mName = name;
142         }
143 
setDescriptor(byte[] descriptor)144         public void setDescriptor(byte[] descriptor) {
145             mEvent.mDescriptor = descriptor;
146         }
147 
setReport(byte[] report)148         public void setReport(byte[] report) {
149             mEvent.mReport = report;
150         }
151 
setFeatureReports(SparseArray<byte[]> reports)152         public void setFeatureReports(SparseArray<byte[]> reports) {
153             mEvent.mFeatureReports = reports;
154         }
155 
setOutputs(Map<ByteBuffer, byte[]> outputs)156         public void setOutputs(Map<ByteBuffer, byte[]> outputs) {
157             mEvent.mOutputs = outputs;
158         }
159 
setVid(int vid)160         public void setVid(int vid) {
161             mEvent.mVid = vid;
162         }
163 
setPid(int pid)164         public void setPid(int pid) {
165             mEvent.mPid = pid;
166         }
167 
setBus(Bus bus)168         public void setBus(Bus bus) {
169             mEvent.mBus = bus;
170         }
171 
setDuration(int duration)172         public void setDuration(int duration) {
173             mEvent.mDuration = duration;
174         }
175 
build()176         public Event build() {
177             if (mEvent.mId == -1) {
178                 throw new IllegalStateException("No event id");
179             } else if (mEvent.mCommand == null) {
180                 throw new IllegalStateException("Event does not contain a command");
181             }
182             if (COMMAND_REGISTER.equals(mEvent.mCommand)) {
183                 if (mEvent.mDescriptor == null) {
184                     throw new IllegalStateException("Device registration is missing descriptor");
185                 }
186             } else if (COMMAND_DELAY.equals(mEvent.mCommand)) {
187                 if (mEvent.mDuration <= 0) {
188                     throw new IllegalStateException("Delay has missing or invalid duration");
189                 }
190             } else if (COMMAND_REPORT.equals(mEvent.mCommand)) {
191                 if (mEvent.mReport == null) {
192                     throw new IllegalStateException("Report command is missing report data");
193                 }
194             }
195             return mEvent;
196         }
197     }
198 
199     public static class Reader {
200         private JsonReader mReader;
201 
Reader(InputStreamReader in)202         public Reader(InputStreamReader in) {
203             mReader = new JsonReader(in);
204             mReader.setLenient(true);
205         }
206 
getNextEvent()207         public Event getNextEvent() throws IOException {
208             Event e = null;
209             while (e == null && mReader.peek() != JsonToken.END_DOCUMENT) {
210                 Event.Builder eb = new Event.Builder();
211                 try {
212                     mReader.beginObject();
213                     while (mReader.hasNext()) {
214                         String name = mReader.nextName();
215                         switch (name) {
216                             case "id":
217                                 eb.setId(readInt());
218                                 break;
219                             case "command":
220                                 eb.setCommand(mReader.nextString());
221                                 break;
222                             case "descriptor":
223                                 eb.setDescriptor(readData());
224                                 break;
225                             case "name":
226                                 eb.setName(mReader.nextString());
227                                 break;
228                             case "vid":
229                                 eb.setVid(readInt());
230                                 break;
231                             case "pid":
232                                 eb.setPid(readInt());
233                                 break;
234                             case "bus":
235                                 eb.setBus(readBus());
236                                 break;
237                             case "report":
238                                 eb.setReport(readData());
239                                 break;
240                             case "feature_reports":
241                                 eb.setFeatureReports(readFeatureReports());
242                                 break;
243                             case "outputs":
244                                 eb.setOutputs(readOutputs());
245                                 break;
246                             case "duration":
247                                 eb.setDuration(readInt());
248                                 break;
249                             default:
250                                 mReader.skipValue();
251                         }
252                     }
253                     mReader.endObject();
254                 } catch (IllegalStateException ex) {
255                     error("Error reading in object, ignoring.", ex);
256                     consumeRemainingElements();
257                     mReader.endObject();
258                     continue;
259                 }
260                 e = eb.build();
261             }
262 
263             return e;
264         }
265 
readData()266         private byte[] readData() throws IOException {
267             ArrayList<Integer> data = new ArrayList<Integer>();
268             try {
269                 mReader.beginArray();
270                 while (mReader.hasNext()) {
271                     data.add(Integer.decode(mReader.nextString()));
272                 }
273                 mReader.endArray();
274             } catch (IllegalStateException|NumberFormatException e) {
275                 consumeRemainingElements();
276                 mReader.endArray();
277                 throw new IllegalStateException("Encountered malformed data.", e);
278             }
279             byte[] rawData = new byte[data.size()];
280             for (int i = 0; i < data.size(); i++) {
281                 int d = data.get(i);
282                 if ((d & 0xFF) != d) {
283                     throw new IllegalStateException("Invalid data, all values must be byte-sized");
284                 }
285                 rawData[i] = (byte)d;
286             }
287             return rawData;
288         }
289 
readInt()290         private int readInt() throws IOException {
291             String val = mReader.nextString();
292             return Integer.decode(val);
293         }
294 
readBus()295         private Bus readBus() throws IOException {
296             String val = mReader.nextString();
297             return Bus.valueOf(val.toUpperCase());
298         }
299 
readFeatureReports()300         private SparseArray<byte[]> readFeatureReports()
301                 throws IllegalStateException, IOException {
302             SparseArray<byte[]> featureReports = new SparseArray<>();
303             try {
304                 mReader.beginArray();
305                 while (mReader.hasNext()) {
306                     // If "id" is not specified, it defaults to 0, which means
307                     // report does not contain report ID (based on HID specs).
308                     int id = 0;
309                     byte[] data = null;
310                     mReader.beginObject();
311                     while (mReader.hasNext()) {
312                         String name = mReader.nextName();
313                         switch (name) {
314                             case "id":
315                                 id = readInt();
316                                 break;
317                             case "data":
318                                 data = readData();
319                                 break;
320                             default:
321                                 consumeRemainingElements();
322                                 mReader.endObject();
323                                 throw new IllegalStateException("Invalid key in feature report: "
324                                         + name);
325                         }
326                     }
327                     mReader.endObject();
328                     if (data != null) {
329                         featureReports.put(id, data);
330                     }
331                 }
332                 mReader.endArray();
333             } catch (IllegalStateException | NumberFormatException e) {
334                 consumeRemainingElements();
335                 mReader.endArray();
336                 throw new IllegalStateException("Encountered malformed data.", e);
337             }
338             return featureReports;
339         }
340 
readOutputs()341         private Map<ByteBuffer, byte[]> readOutputs()
342                 throws IllegalStateException, IOException {
343             Map<ByteBuffer, byte[]> outputs = new HashMap<>();
344 
345             try {
346                 mReader.beginArray();
347                 while (mReader.hasNext()) {
348                     byte[] output = null;
349                     byte[] response = null;
350                     mReader.beginObject();
351                     while (mReader.hasNext()) {
352                         String name = mReader.nextName();
353                         switch (name) {
354                             case "description":
355                                 // Description is only used to keep track of the output responses
356                                 mReader.nextString();
357                                 break;
358                             case "output":
359                                 output = readData();
360                                 break;
361                             case "response":
362                                 response = readData();
363                                 break;
364                             default:
365                                 consumeRemainingElements();
366                                 mReader.endObject();
367                                 throw new IllegalStateException("Invalid key in outputs: " + name);
368                         }
369                     }
370                     mReader.endObject();
371                     if (output != null) {
372                         outputs.put(ByteBuffer.wrap(output), response);
373                     }
374                 }
375                 mReader.endArray();
376             } catch (IllegalStateException | NumberFormatException e) {
377                 consumeRemainingElements();
378                 mReader.endArray();
379                 throw new IllegalStateException("Encountered malformed data.", e);
380             }
381             return outputs;
382         }
383 
consumeRemainingElements()384         private void consumeRemainingElements() throws IOException {
385             while (mReader.hasNext()) {
386                 mReader.skipValue();
387             }
388         }
389     }
390 
error(String msg, Exception e)391     private static void error(String msg, Exception e) {
392         System.out.println(msg);
393         Log.e(TAG, msg);
394         if (e != null) {
395             Log.e(TAG, Log.getStackTraceString(e));
396         }
397     }
398 }
399