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