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.os.Handler;
20 import android.os.HandlerThread;
21 import android.os.Looper;
22 import android.os.Message;
23 import android.os.SystemClock;
24 import android.util.Log;
25 import android.util.SparseArray;
26 
27 import com.android.internal.os.SomeArgs;
28 
29 import org.json.JSONArray;
30 import org.json.JSONException;
31 import org.json.JSONObject;
32 
33 import java.io.IOException;
34 import java.io.OutputStream;
35 import java.nio.ByteBuffer;
36 import java.util.Arrays;
37 import java.util.Map;
38 
39 public class Device {
40     private static final String TAG = "HidDevice";
41 
42     private static final int MSG_OPEN_DEVICE = 1;
43     private static final int MSG_SEND_REPORT = 2;
44     private static final int MSG_SEND_GET_FEATURE_REPORT_REPLY = 3;
45     private static final int MSG_SEND_SET_REPORT_REPLY = 4;
46     private static final int MSG_CLOSE_DEVICE = 5;
47 
48     // Sync with linux uhid_event_type::UHID_OUTPUT
49     private static final byte UHID_EVENT_TYPE_UHID_OUTPUT = 6;
50     // Sync with linux uhid_event_type::UHID_SET_REPORT
51     private static final byte UHID_EVENT_TYPE_SET_REPORT = 13;
52     private final int mId;
53     private final HandlerThread mThread;
54     private final DeviceHandler mHandler;
55     // mFeatureReports is limited to 256 entries, because the report number is 8-bit
56     private final SparseArray<byte[]> mFeatureReports;
57     private final Map<ByteBuffer, byte[]> mOutputs;
58     private final OutputStream mOutputStream;
59     private long mTimeToSend;
60     private final Object mCond = new Object();
61     /**
62      * The report id of the report received in UHID_EVENT_TYPE_SET_REPORT.
63      * Used for SET_REPORT_REPLY.
64      * This field gets overridden each time SET_REPORT is received.
65      */
66     private int mResponseId;
67 
68     static {
69         System.loadLibrary("hidcommand_jni");
70     }
71 
nativeOpenDevice( String name, int id, int vid, int pid, int bus, byte[] descriptor, DeviceCallback callback)72     private static native long nativeOpenDevice(
73             String name,
74             int id,
75             int vid,
76             int pid,
77             int bus,
78             byte[] descriptor,
79             DeviceCallback callback);
80 
nativeSendReport(long ptr, byte[] data)81     private static native void nativeSendReport(long ptr, byte[] data);
82 
nativeSendGetFeatureReportReply(long ptr, int id, byte[] data)83     private static native void nativeSendGetFeatureReportReply(long ptr, int id, byte[] data);
84 
nativeSendSetReportReply(long ptr, int id, boolean success)85     private static native void nativeSendSetReportReply(long ptr, int id, boolean success);
86 
nativeCloseDevice(long ptr)87     private static native void nativeCloseDevice(long ptr);
88 
Device( int id, String name, int vid, int pid, int bus, byte[] descriptor, byte[] report, SparseArray<byte[]> featureReports, Map<ByteBuffer, byte[]> outputs)89     public Device(
90             int id,
91             String name,
92             int vid,
93             int pid,
94             int bus,
95             byte[] descriptor,
96             byte[] report,
97             SparseArray<byte[]> featureReports,
98             Map<ByteBuffer, byte[]> outputs) {
99         mId = id;
100         mThread = new HandlerThread("HidDeviceHandler");
101         mThread.start();
102         mHandler = new DeviceHandler(mThread.getLooper());
103         mFeatureReports = featureReports;
104         mOutputs = outputs;
105         mOutputStream = System.out;
106         SomeArgs args = SomeArgs.obtain();
107         args.argi1 = id;
108         args.argi2 = vid;
109         args.argi3 = pid;
110         args.argi4 = bus;
111         if (name != null) {
112             args.arg1 = name;
113         } else {
114             args.arg1 = id + ":" + vid + ":" + pid;
115         }
116         args.arg2 = descriptor;
117         args.arg3 = report;
118         mHandler.obtainMessage(MSG_OPEN_DEVICE, args).sendToTarget();
119         mTimeToSend = SystemClock.uptimeMillis();
120     }
121 
sendReport(byte[] report)122     public void sendReport(byte[] report) {
123         Message msg = mHandler.obtainMessage(MSG_SEND_REPORT, report);
124         // if two messages are sent at identical time, they will be processed in order received
125         mHandler.sendMessageAtTime(msg, mTimeToSend);
126     }
127 
setGetReportResponse(byte[] report)128     public void setGetReportResponse(byte[] report) {
129         mFeatureReports.put(report[0], report);
130     }
131 
sendSetReportReply(boolean success)132     public void sendSetReportReply(boolean success) {
133         Message msg =
134                 mHandler.obtainMessage(MSG_SEND_SET_REPORT_REPLY, mResponseId, success ? 1 : 0);
135 
136         mHandler.sendMessageAtTime(msg, mTimeToSend);
137     }
138 
addDelay(int delay)139     public void addDelay(int delay) {
140         mTimeToSend = Math.max(SystemClock.uptimeMillis(), mTimeToSend) + delay;
141     }
142 
close()143     public void close() {
144         Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE);
145         mHandler.sendMessageAtTime(msg, Math.max(SystemClock.uptimeMillis(), mTimeToSend) + 1);
146         try {
147             synchronized (mCond) {
148                 mCond.wait();
149             }
150         } catch (InterruptedException ignore) {
151         }
152     }
153 
154     private class DeviceHandler extends Handler {
155         private long mPtr;
156         private int mBarrierToken;
157 
DeviceHandler(Looper looper)158         public DeviceHandler(Looper looper) {
159             super(looper);
160         }
161 
162         @Override
handleMessage(Message msg)163         public void handleMessage(Message msg) {
164             switch (msg.what) {
165                 case MSG_OPEN_DEVICE:
166                     SomeArgs args = (SomeArgs) msg.obj;
167                     mPtr =
168                             nativeOpenDevice(
169                                     (String) args.arg1,
170                                     args.argi1,
171                                     args.argi2,
172                                     args.argi3,
173                                     args.argi4,
174                                     (byte[]) args.arg2,
175                                     new DeviceCallback());
176                     pauseEvents();
177                     break;
178                 case MSG_SEND_REPORT:
179                     if (mPtr != 0) {
180                         nativeSendReport(mPtr, (byte[]) msg.obj);
181                     } else {
182                         Log.e(TAG, "Tried to send report to closed device.");
183                     }
184                     break;
185                 case MSG_SEND_GET_FEATURE_REPORT_REPLY:
186                     if (mPtr != 0) {
187                         nativeSendGetFeatureReportReply(mPtr, msg.arg1, (byte[]) msg.obj);
188                     } else {
189                         Log.e(TAG, "Tried to send feature report reply to closed device.");
190                     }
191                     break;
192                 case MSG_SEND_SET_REPORT_REPLY:
193                     if (mPtr != 0) {
194                         final boolean success = msg.arg2 == 1;
195                         nativeSendSetReportReply(mPtr, msg.arg1, success);
196                     } else {
197                         Log.e(TAG, "Tried to send set report reply to closed device.");
198                     }
199                     break;
200                 case MSG_CLOSE_DEVICE:
201                     if (mPtr != 0) {
202                         nativeCloseDevice(mPtr);
203                         getLooper().quitSafely();
204                         mPtr = 0;
205                     } else {
206                         Log.e(TAG, "Tried to close already closed device.");
207                     }
208                     synchronized (mCond) {
209                         mCond.notify();
210                     }
211                     break;
212                 default:
213                     throw new IllegalArgumentException("Unknown device message");
214             }
215         }
216 
pauseEvents()217         public void pauseEvents() {
218             mBarrierToken = getLooper().myQueue().postSyncBarrier();
219         }
220 
resumeEvents()221         public void resumeEvents() {
222             getLooper().myQueue().removeSyncBarrier(mBarrierToken);
223             mBarrierToken = 0;
224         }
225     }
226 
227     private class DeviceCallback {
228 
onDeviceOpen()229         public void onDeviceOpen() {
230             mHandler.resumeEvents();
231         }
232 
onDeviceGetReport(int requestId, int reportId)233         public void onDeviceGetReport(int requestId, int reportId) {
234             if (mFeatureReports == null) {
235                 Log.e(
236                         TAG,
237                         "Received GET_REPORT request for reportId="
238                                 + reportId
239                                 + ", but 'feature_reports' section is not found");
240                 return;
241             }
242             byte[] report = mFeatureReports.get(reportId);
243 
244             if (report == null) {
245                 Log.e(TAG, "Requested feature report " + reportId + " is not specified");
246             }
247 
248             Message msg;
249             msg = mHandler.obtainMessage(MSG_SEND_GET_FEATURE_REPORT_REPLY, requestId, 0, report);
250 
251             // Message is set to asynchronous so it won't be blocked by synchronization
252             // barrier during UHID_OPEN. This is necessary for drivers that do
253             // UHID_GET_REPORT requests during probe.
254             msg.setAsynchronous(true);
255             mHandler.sendMessageAtTime(msg, mTimeToSend);
256         }
257 
258         // Send out the report to HID command output
sendReportOutput(byte eventId, byte rtype, byte[] data)259         private void sendReportOutput(byte eventId, byte rtype, byte[] data) {
260             JSONObject json = new JSONObject();
261             try {
262                 json.put("eventId", eventId);
263                 json.put("deviceId", mId);
264                 json.put("reportType", rtype);
265                 JSONArray dataArray = new JSONArray();
266                 for (int i = 0; i < data.length; i++) {
267                     dataArray.put(data[i] & 0xFF);
268                 }
269                 json.put("reportData", dataArray);
270             } catch (JSONException e) {
271                 throw new RuntimeException("Could not create JSON object ", e);
272             }
273             try {
274                 mOutputStream.write(json.toString().getBytes());
275                 mOutputStream.flush();
276             } catch (IOException e) {
277                 throw new RuntimeException(e);
278             }
279         }
280 
281         // native callback
onDeviceSetReport(int id, byte rType, byte[] data)282         public void onDeviceSetReport(int id, byte rType, byte[] data) {
283             // Used by sendSetReportReply()
284             mResponseId = id;
285             // We don't need to reply for the SET_REPORT but just send it to HID output for test
286             // verification.
287             sendReportOutput(UHID_EVENT_TYPE_SET_REPORT, rType, data);
288         }
289 
290         // native callback
onDeviceOutput(byte rtype, byte[] data)291         public void onDeviceOutput(byte rtype, byte[] data) {
292             sendReportOutput(UHID_EVENT_TYPE_UHID_OUTPUT, rtype, data);
293             if (mOutputs == null) {
294                 Log.e(TAG, "Received OUTPUT request, but 'outputs' section is not found");
295                 return;
296             }
297             byte[] response = mOutputs.get(ByteBuffer.wrap(data));
298             if (response == null) {
299                 Log.i(
300                         TAG,
301                         "Requested response for output " + Arrays.toString(data) + " is not found");
302                 return;
303             }
304 
305             Message msg;
306             msg = mHandler.obtainMessage(MSG_SEND_REPORT, response);
307 
308             // Message is set to asynchronous so it won't be blocked by synchronization
309             // barrier during UHID_OPEN. This is necessary for drivers that do
310             // UHID_OUTPUT requests during probe, and expect a response right away.
311             msg.setAsynchronous(true);
312             mHandler.sendMessageAtTime(msg, mTimeToSend);
313         }
314 
onDeviceError()315         public void onDeviceError() {
316             Log.e(TAG, "Device error occurred, closing /dev/uhid");
317             Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE);
318             msg.setAsynchronous(true);
319             msg.sendToTarget();
320         }
321     }
322 }
323