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