OLD | NEW |
| (Empty) |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package org.chromium.media.midi; | |
6 | |
7 import android.annotation.TargetApi; | |
8 import android.hardware.usb.UsbConstants; | |
9 import android.hardware.usb.UsbDevice; | |
10 import android.hardware.usb.UsbDeviceConnection; | |
11 import android.hardware.usb.UsbEndpoint; | |
12 import android.hardware.usb.UsbInterface; | |
13 import android.hardware.usb.UsbManager; | |
14 import android.hardware.usb.UsbRequest; | |
15 import android.os.Build; | |
16 import android.os.Handler; | |
17 import android.util.SparseArray; | |
18 | |
19 import org.chromium.base.annotations.CalledByNative; | |
20 import org.chromium.base.annotations.JNINamespace; | |
21 | |
22 import java.nio.ByteBuffer; | |
23 import java.util.Arrays; | |
24 import java.util.HashMap; | |
25 import java.util.Map; | |
26 | |
27 /** | |
28 * Owned by its native counterpart declared in usb_midi_device_android.h. | |
29 * Refer to that class for general comments. | |
30 */ | |
31 @JNINamespace("media::midi") | |
32 class UsbMidiDeviceAndroid { | |
33 /** | |
34 * A connection handle for this device. | |
35 */ | |
36 private final UsbDeviceConnection mConnection; | |
37 | |
38 /** | |
39 * A map from endpoint number to UsbEndpoint. | |
40 */ | |
41 private final SparseArray<UsbEndpoint> mEndpointMap; | |
42 | |
43 /** | |
44 * A map from UsbEndpoint to UsbRequest associated to it. | |
45 */ | |
46 private final Map<UsbEndpoint, UsbRequest> mRequestMap; | |
47 | |
48 /** | |
49 * The handler used for posting events on the main thread. | |
50 */ | |
51 private final Handler mHandler; | |
52 | |
53 /** | |
54 * True if this device is closed. | |
55 */ | |
56 private boolean mIsClosed; | |
57 | |
58 /** | |
59 * True if there is a thread processing input data. | |
60 */ | |
61 private boolean mHasInputThread; | |
62 | |
63 /** | |
64 * The identifier of this device. | |
65 */ | |
66 private long mNativePointer; | |
67 | |
68 /** | |
69 * The underlying USB device. | |
70 */ | |
71 private UsbDevice mUsbDevice; | |
72 | |
73 /** | |
74 * Audio interface subclass code for MIDI. | |
75 */ | |
76 static final int MIDI_SUBCLASS = 3; | |
77 | |
78 /** | |
79 * The request type to request a USB descriptor. | |
80 */ | |
81 static final int REQUEST_GET_DESCRIPTOR = 0x06; | |
82 | |
83 /** | |
84 * The STRING descriptor type. | |
85 */ | |
86 static final int STRING_DESCRIPTOR_TYPE = 0x03; | |
87 | |
88 /** | |
89 * Constructs a UsbMidiDeviceAndroid. | |
90 * @param manager | |
91 * @param device The USB device which this object is assocated with. | |
92 */ | |
93 UsbMidiDeviceAndroid(UsbManager manager, UsbDevice device) { | |
94 mConnection = manager.openDevice(device); | |
95 mEndpointMap = new SparseArray<UsbEndpoint>(); | |
96 mRequestMap = new HashMap<UsbEndpoint, UsbRequest>(); | |
97 mHandler = new Handler(); | |
98 mUsbDevice = device; | |
99 mIsClosed = false; | |
100 mHasInputThread = false; | |
101 mNativePointer = 0; | |
102 | |
103 for (int i = 0; i < device.getInterfaceCount(); ++i) { | |
104 UsbInterface iface = device.getInterface(i); | |
105 if (iface.getInterfaceClass() != UsbConstants.USB_CLASS_AUDIO | |
106 || iface.getInterfaceSubclass() != MIDI_SUBCLASS) { | |
107 continue; | |
108 } | |
109 mConnection.claimInterface(iface, true); | |
110 for (int j = 0; j < iface.getEndpointCount(); ++j) { | |
111 UsbEndpoint endpoint = iface.getEndpoint(j); | |
112 if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) { | |
113 mEndpointMap.put(endpoint.getEndpointNumber(), endpoint); | |
114 } | |
115 } | |
116 } | |
117 // Start listening for input endpoints. | |
118 // This function will create and run a thread if there is USB-MIDI endpo
ints in the | |
119 // device. Note that because UsbMidiDevice is shared among all tabs and
the thread | |
120 // will be terminated when the device is disconnected, at most one threa
d can be created | |
121 // for each connected USB-MIDI device. | |
122 startListen(device); | |
123 } | |
124 | |
125 /** | |
126 * Starts listening for input endpoints. | |
127 */ | |
128 private void startListen(final UsbDevice device) { | |
129 final Map<UsbEndpoint, ByteBuffer> bufferForEndpoints = | |
130 new HashMap<UsbEndpoint, ByteBuffer>(); | |
131 | |
132 for (int i = 0; i < device.getInterfaceCount(); ++i) { | |
133 UsbInterface iface = device.getInterface(i); | |
134 if (iface.getInterfaceClass() != UsbConstants.USB_CLASS_AUDIO | |
135 || iface.getInterfaceSubclass() != MIDI_SUBCLASS) { | |
136 continue; | |
137 } | |
138 for (int j = 0; j < iface.getEndpointCount(); ++j) { | |
139 UsbEndpoint endpoint = iface.getEndpoint(j); | |
140 if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) { | |
141 ByteBuffer buffer = ByteBuffer.allocate(endpoint.getMaxPacke
tSize()); | |
142 UsbRequest request = new UsbRequest(); | |
143 request.initialize(mConnection, endpoint); | |
144 request.queue(buffer, buffer.remaining()); | |
145 bufferForEndpoints.put(endpoint, buffer); | |
146 } | |
147 } | |
148 } | |
149 if (bufferForEndpoints.isEmpty()) { | |
150 return; | |
151 } | |
152 mHasInputThread = true; | |
153 // bufferForEndpoints must not be accessed hereafter on this thread. | |
154 new Thread() { | |
155 @Override | |
156 public void run() { | |
157 while (true) { | |
158 UsbRequest request = mConnection.requestWait(); | |
159 if (request == null) { | |
160 // When the device is closed requestWait will fail. | |
161 break; | |
162 } | |
163 UsbEndpoint endpoint = request.getEndpoint(); | |
164 if (endpoint.getDirection() != UsbConstants.USB_DIR_IN) { | |
165 continue; | |
166 } | |
167 ByteBuffer buffer = bufferForEndpoints.get(endpoint); | |
168 int length = getInputDataLength(buffer); | |
169 if (length > 0) { | |
170 buffer.rewind(); | |
171 final byte[] bs = new byte[length]; | |
172 buffer.get(bs, 0, length); | |
173 postOnDataEvent(endpoint.getEndpointNumber(), bs); | |
174 } | |
175 buffer.rewind(); | |
176 request.queue(buffer, buffer.capacity()); | |
177 } | |
178 } | |
179 }.start(); | |
180 } | |
181 | |
182 /** | |
183 * Posts a data input event to the main thread. | |
184 */ | |
185 private void postOnDataEvent(final int endpointNumber, final byte[] bs) { | |
186 mHandler.post(new Runnable() { | |
187 @Override | |
188 public void run() { | |
189 if (mIsClosed) { | |
190 return; | |
191 } | |
192 nativeOnData(mNativePointer, endpointNumber, bs); | |
193 } | |
194 }); | |
195 } | |
196 | |
197 UsbDevice getUsbDevice() { | |
198 return mUsbDevice; | |
199 } | |
200 | |
201 boolean isClosed() { | |
202 return mIsClosed; | |
203 } | |
204 | |
205 /** | |
206 * Register the own native pointer. | |
207 */ | |
208 @CalledByNative | |
209 void registerSelf(long nativePointer) { | |
210 mNativePointer = nativePointer; | |
211 } | |
212 | |
213 /** | |
214 * Sends a USB-MIDI data to the device. | |
215 * @param endpointNumber The endpoint number of the destination endpoint. | |
216 * @param bs The data to be sent. | |
217 */ | |
218 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) | |
219 @CalledByNative | |
220 void send(int endpointNumber, byte[] bs) { | |
221 if (mIsClosed) { | |
222 return; | |
223 } | |
224 UsbEndpoint endpoint = mEndpointMap.get(endpointNumber); | |
225 if (endpoint == null) { | |
226 return; | |
227 } | |
228 if (shouldUseBulkTransfer()) { | |
229 // We use bulkTransfer instead of UsbRequest.queue because queueing | |
230 // a UsbRequest is currently not thread safe. | |
231 // Note that this is not exactly correct because we don't care | |
232 // about the transfer attribute (bmAttribute) of the endpoint. | |
233 // See also: | |
234 // http://stackoverflow.com/questions/9644415/ | |
235 // https://code.google.com/p/android/issues/detail?id=59467 | |
236 // | |
237 // TODO(yhirano): Delete this block once the problem is fixed. | |
238 final int timeout = 100; | |
239 mConnection.bulkTransfer(endpoint, bs, bs.length, timeout); | |
240 } else { | |
241 UsbRequest request = mRequestMap.get(endpoint); | |
242 if (request == null) { | |
243 request = new UsbRequest(); | |
244 request.initialize(mConnection, endpoint); | |
245 mRequestMap.put(endpoint, request); | |
246 } | |
247 request.queue(ByteBuffer.wrap(bs), bs.length); | |
248 } | |
249 } | |
250 | |
251 /** | |
252 * Returns true if |bulkTransfer| should be used in |send|. | |
253 * See comments in |send|. | |
254 */ | |
255 private boolean shouldUseBulkTransfer() { | |
256 return mHasInputThread; | |
257 } | |
258 | |
259 /** | |
260 * Returns the descriptors bytes of this device. | |
261 * @return The descriptors bytes of this device. | |
262 */ | |
263 @CalledByNative | |
264 byte[] getDescriptors() { | |
265 if (mConnection == null) { | |
266 return new byte[0]; | |
267 } | |
268 return mConnection.getRawDescriptors(); | |
269 } | |
270 | |
271 /** | |
272 * Returns the string descriptor bytes for the given index | |
273 * @param index index of the descriptor | |
274 * @return the string descriptor bytes for the given index. | |
275 */ | |
276 @CalledByNative | |
277 byte[] getStringDescriptor(int index) { | |
278 if (mConnection == null) { | |
279 return new byte[0]; | |
280 } | |
281 byte[] buffer = new byte[255]; | |
282 int type = UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_STANDARD; | |
283 int request = REQUEST_GET_DESCRIPTOR; | |
284 int value = (STRING_DESCRIPTOR_TYPE << 8) | index; | |
285 int read = mConnection.controlTransfer(type, request, value, 0, buffer,
buffer.length, 0); | |
286 if (read < 0) { | |
287 return new byte[0]; | |
288 } | |
289 return Arrays.copyOf(buffer, read); | |
290 } | |
291 | |
292 /** | |
293 * Closes the device connection. | |
294 */ | |
295 @CalledByNative | |
296 void close() { | |
297 mEndpointMap.clear(); | |
298 for (UsbRequest request : mRequestMap.values()) { | |
299 request.close(); | |
300 } | |
301 mRequestMap.clear(); | |
302 mConnection.close(); | |
303 mNativePointer = 0; | |
304 mIsClosed = true; | |
305 } | |
306 | |
307 /** | |
308 * Returns the length of a USB-MIDI input. | |
309 * Since the Android API doesn't provide us the length, | |
310 * we calculate it manually. | |
311 */ | |
312 private static int getInputDataLength(ByteBuffer buffer) { | |
313 int position = buffer.position(); | |
314 // We assume that the data length is always divisable by 4. | |
315 for (int i = 0; i < position; i += 4) { | |
316 // Since Code Index Number 0 is reserved, it is not a valid USB-MIDI
data. | |
317 if (buffer.get(i) == 0) { | |
318 return i; | |
319 } | |
320 } | |
321 return position; | |
322 } | |
323 | |
324 private static native void nativeOnData( | |
325 long nativeUsbMidiDeviceAndroid, int endpointNumber, byte[] data); | |
326 } | |
OLD | NEW |