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 java.nio.ByteBuffer; 30 import java.util.Arrays; 31 import java.util.Map; 32 33 public class Device { 34 private static final String TAG = "HidDevice"; 35 36 private static final int MSG_OPEN_DEVICE = 1; 37 private static final int MSG_SEND_REPORT = 2; 38 private static final int MSG_SEND_GET_FEATURE_REPORT_REPLY = 3; 39 private static final int MSG_CLOSE_DEVICE = 4; 40 41 private final int mId; 42 private final HandlerThread mThread; 43 private final DeviceHandler mHandler; 44 // mFeatureReports is limited to 256 entries, because the report number is 8-bit 45 private final SparseArray<byte[]> mFeatureReports; 46 private final Map<ByteBuffer, byte[]> mOutputs; 47 private long mTimeToSend; 48 49 private final Object mCond = new Object(); 50 51 static { 52 System.loadLibrary("hidcommand_jni"); 53 } 54 nativeOpenDevice(String name, int id, int vid, int pid, int bus, byte[] descriptor, DeviceCallback callback)55 private static native long nativeOpenDevice(String name, int id, int vid, int pid, int bus, 56 byte[] descriptor, DeviceCallback callback); nativeSendReport(long ptr, byte[] data)57 private static native void nativeSendReport(long ptr, byte[] data); nativeSendGetFeatureReportReply(long ptr, int id, byte[] data)58 private static native void nativeSendGetFeatureReportReply(long ptr, int id, byte[] data); nativeCloseDevice(long ptr)59 private static native void nativeCloseDevice(long ptr); 60 Device(int id, String name, int vid, int pid, int bus, byte[] descriptor, byte[] report, SparseArray<byte[]> featureReports, Map<ByteBuffer, byte[]> outputs)61 public Device(int id, String name, int vid, int pid, int bus, byte[] descriptor, 62 byte[] report, SparseArray<byte[]> featureReports, Map<ByteBuffer, byte[]> outputs) { 63 mId = id; 64 mThread = new HandlerThread("HidDeviceHandler"); 65 mThread.start(); 66 mHandler = new DeviceHandler(mThread.getLooper()); 67 mFeatureReports = featureReports; 68 mOutputs = outputs; 69 SomeArgs args = SomeArgs.obtain(); 70 args.argi1 = id; 71 args.argi2 = vid; 72 args.argi3 = pid; 73 args.argi4 = bus; 74 if (name != null) { 75 args.arg1 = name; 76 } else { 77 args.arg1 = id + ":" + vid + ":" + pid; 78 } 79 args.arg2 = descriptor; 80 args.arg3 = report; 81 mHandler.obtainMessage(MSG_OPEN_DEVICE, args).sendToTarget(); 82 mTimeToSend = SystemClock.uptimeMillis(); 83 } 84 sendReport(byte[] report)85 public void sendReport(byte[] report) { 86 Message msg = mHandler.obtainMessage(MSG_SEND_REPORT, report); 87 // if two messages are sent at identical time, they will be processed in order received 88 mHandler.sendMessageAtTime(msg, mTimeToSend); 89 } 90 addDelay(int delay)91 public void addDelay(int delay) { 92 mTimeToSend = Math.max(SystemClock.uptimeMillis(), mTimeToSend) + delay; 93 } 94 close()95 public void close() { 96 Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE); 97 mHandler.sendMessageAtTime(msg, Math.max(SystemClock.uptimeMillis(), mTimeToSend) + 1); 98 try { 99 synchronized (mCond) { 100 mCond.wait(); 101 } 102 } catch (InterruptedException ignore) {} 103 } 104 105 private class DeviceHandler extends Handler { 106 private long mPtr; 107 private int mBarrierToken; 108 DeviceHandler(Looper looper)109 public DeviceHandler(Looper looper) { 110 super(looper); 111 } 112 113 @Override handleMessage(Message msg)114 public void handleMessage(Message msg) { 115 switch (msg.what) { 116 case MSG_OPEN_DEVICE: 117 SomeArgs args = (SomeArgs) msg.obj; 118 mPtr = nativeOpenDevice((String) args.arg1, args.argi1, args.argi2, args.argi3, 119 args.argi4, (byte[]) args.arg2, new DeviceCallback()); 120 pauseEvents(); 121 break; 122 case MSG_SEND_REPORT: 123 if (mPtr != 0) { 124 nativeSendReport(mPtr, (byte[]) msg.obj); 125 } else { 126 Log.e(TAG, "Tried to send report to closed device."); 127 } 128 break; 129 case MSG_SEND_GET_FEATURE_REPORT_REPLY: 130 if (mPtr != 0) { 131 nativeSendGetFeatureReportReply(mPtr, msg.arg1, (byte[]) msg.obj); 132 } else { 133 Log.e(TAG, "Tried to send feature report reply to closed device."); 134 } 135 break; 136 case MSG_CLOSE_DEVICE: 137 if (mPtr != 0) { 138 nativeCloseDevice(mPtr); 139 getLooper().quitSafely(); 140 mPtr = 0; 141 } else { 142 Log.e(TAG, "Tried to close already closed device."); 143 } 144 synchronized (mCond) { 145 mCond.notify(); 146 } 147 break; 148 default: 149 throw new IllegalArgumentException("Unknown device message"); 150 } 151 } 152 pauseEvents()153 public void pauseEvents() { 154 mBarrierToken = getLooper().myQueue().postSyncBarrier(); 155 } 156 resumeEvents()157 public void resumeEvents() { 158 getLooper().myQueue().removeSyncBarrier(mBarrierToken); 159 mBarrierToken = 0; 160 } 161 } 162 163 private class DeviceCallback { onDeviceOpen()164 public void onDeviceOpen() { 165 mHandler.resumeEvents(); 166 } 167 onDeviceGetReport(int requestId, int reportId)168 public void onDeviceGetReport(int requestId, int reportId) { 169 if (mFeatureReports == null) { 170 Log.e(TAG, "Received GET_REPORT request for reportId=" + reportId 171 + ", but 'feature_reports' section is not found"); 172 return; 173 } 174 byte[] report = mFeatureReports.get(reportId); 175 176 if (report == null) { 177 Log.e(TAG, "Requested feature report " + reportId + " is not specified"); 178 } 179 180 Message msg; 181 msg = mHandler.obtainMessage(MSG_SEND_GET_FEATURE_REPORT_REPLY, requestId, 0, report); 182 183 // Message is set to asynchronous so it won't be blocked by synchronization 184 // barrier during UHID_OPEN. This is necessary for drivers that do 185 // UHID_GET_REPORT requests during probe. 186 msg.setAsynchronous(true); 187 mHandler.sendMessageAtTime(msg, mTimeToSend); 188 } 189 190 // native callback onDeviceOutput(byte[] data)191 public void onDeviceOutput(byte[] data) { 192 if (mOutputs == null) { 193 Log.e(TAG, "Received OUTPUT request, but 'outputs' section is not found"); 194 return; 195 } 196 byte[] response = mOutputs.get(ByteBuffer.wrap(data)); 197 if (response == null) { 198 Log.i(TAG, 199 "Requested response for output " + Arrays.toString(data) + " is not found"); 200 return; 201 } 202 203 Message msg; 204 msg = mHandler.obtainMessage(MSG_SEND_REPORT, response); 205 206 // Message is set to asynchronous so it won't be blocked by synchronization 207 // barrier during UHID_OPEN. This is necessary for drivers that do 208 // UHID_OUTPUT requests during probe, and expect a response right away. 209 msg.setAsynchronous(true); 210 mHandler.sendMessageAtTime(msg, mTimeToSend); 211 } 212 onDeviceError()213 public void onDeviceError() { 214 Log.e(TAG, "Device error occurred, closing /dev/uhid"); 215 Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE); 216 msg.setAsynchronous(true); 217 msg.sendToTarget(); 218 } 219 } 220 } 221