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