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