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.
5 package org.chromium.media;
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;
19 import org.chromium.base.CalledByNative;
20 import org.chromium.base.JNINamespace;
22 import java.nio.ByteBuffer;
23 import java.util.HashMap;
27 * Owned by its native counterpart declared in usb_midi_device_android.h.
28 * Refer to that class for general comments.
30 @JNINamespace("media")
31 class UsbMidiDeviceAndroid {
33 * A connection handle for this device.
35 private final UsbDeviceConnection mConnection;
38 * A map from endpoint number to UsbEndpoint.
40 private final SparseArray<UsbEndpoint> mEndpointMap;
43 * A map from UsbEndpoint to UsbRequest associated to it.
45 private final Map<UsbEndpoint, UsbRequest> mRequestMap;
48 * The handler used for posting events on the main thread.
50 private final Handler mHandler;
53 * True if this device is closed.
55 private boolean mIsClosed;
58 * True if there is a thread processing input data.
60 private boolean mHasInputThread;
63 * The identifier of this device.
65 private long mNativePointer;
68 * Audio interface subclass code for MIDI.
70 static final int MIDI_SUBCLASS = 3;
73 * Constructs a UsbMidiDeviceAndroid.
75 * @param device The USB device which this object is assocated with.
77 UsbMidiDeviceAndroid(UsbManager manager, UsbDevice device) {
78 mConnection = manager.openDevice(device);
79 mEndpointMap = new SparseArray<UsbEndpoint>();
80 mRequestMap = new HashMap<UsbEndpoint, UsbRequest>();
81 mHandler = new Handler();
83 mHasInputThread = false;
86 for (int i = 0; i < device.getInterfaceCount(); ++i) {
87 UsbInterface iface = device.getInterface(i);
88 if (iface.getInterfaceClass() != UsbConstants.USB_CLASS_AUDIO
89 || iface.getInterfaceSubclass() != MIDI_SUBCLASS) {
92 mConnection.claimInterface(iface, true);
93 for (int j = 0; j < iface.getEndpointCount(); ++j) {
94 UsbEndpoint endpoint = iface.getEndpoint(j);
95 if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) {
96 mEndpointMap.put(endpoint.getEndpointNumber(), endpoint);
100 // Start listening for input endpoints.
101 // This function will create and run a thread if there is USB-MIDI endpoints in the
102 // device. Note that because UsbMidiDevice is shared among all tabs and the thread
103 // will be terminated when the device is disconnected, at most one thread can be created
104 // for each connected USB-MIDI device.
109 * Starts listening for input endpoints.
111 private void startListen(final UsbDevice device) {
112 final Map<UsbEndpoint, ByteBuffer> bufferForEndpoints =
113 new HashMap<UsbEndpoint, ByteBuffer>();
115 for (int i = 0; i < device.getInterfaceCount(); ++i) {
116 UsbInterface iface = device.getInterface(i);
117 if (iface.getInterfaceClass() != UsbConstants.USB_CLASS_AUDIO
118 || iface.getInterfaceSubclass() != MIDI_SUBCLASS) {
121 for (int j = 0; j < iface.getEndpointCount(); ++j) {
122 UsbEndpoint endpoint = iface.getEndpoint(j);
123 if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) {
124 ByteBuffer buffer = ByteBuffer.allocate(endpoint.getMaxPacketSize());
125 UsbRequest request = new UsbRequest();
126 request.initialize(mConnection, endpoint);
127 request.queue(buffer, buffer.remaining());
128 bufferForEndpoints.put(endpoint, buffer);
132 if (bufferForEndpoints.isEmpty()) {
135 mHasInputThread = true;
136 // bufferForEndpoints must not be accessed hereafter on this thread.
141 UsbRequest request = mConnection.requestWait();
142 if (request == null) {
143 // When the device is closed requestWait will fail.
146 UsbEndpoint endpoint = request.getEndpoint();
147 if (endpoint.getDirection() != UsbConstants.USB_DIR_IN) {
150 ByteBuffer buffer = bufferForEndpoints.get(endpoint);
151 int length = getInputDataLength(buffer);
154 final byte[] bs = new byte[length];
155 buffer.get(bs, 0, length);
156 postOnDataEvent(endpoint.getEndpointNumber(), bs);
159 request.queue(buffer, buffer.capacity());
166 * Posts a data input event to the main thread.
168 private void postOnDataEvent(final int endpointNumber, final byte[] bs) {
169 mHandler.post(new Runnable() {
175 nativeOnData(mNativePointer, endpointNumber, bs);
181 * Register the own native pointer.
184 void registerSelf(long nativePointer) {
185 mNativePointer = nativePointer;
189 * Sends a USB-MIDI data to the device.
190 * @param endpointNumber The endpoint number of the destination endpoint.
191 * @param bs The data to be sent.
193 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
195 void send(int endpointNumber, byte[] bs) {
199 UsbEndpoint endpoint = mEndpointMap.get(endpointNumber);
200 if (endpoint == null) {
203 if (shouldUseBulkTransfer()) {
204 // We use bulkTransfer instead of UsbRequest.queue because queueing
205 // a UsbRequest is currently not thread safe.
206 // Note that this is not exactly correct because we don't care
207 // about the transfer attribute (bmAttribute) of the endpoint.
209 // http://stackoverflow.com/questions/9644415/
210 // https://code.google.com/p/android/issues/detail?id=59467
212 // TODO(yhirano): Delete this block once the problem is fixed.
213 final int timeout = 100;
214 mConnection.bulkTransfer(endpoint, bs, bs.length, timeout);
216 UsbRequest request = mRequestMap.get(endpoint);
217 if (request == null) {
218 request = new UsbRequest();
219 request.initialize(mConnection, endpoint);
220 mRequestMap.put(endpoint, request);
222 request.queue(ByteBuffer.wrap(bs), bs.length);
227 * Returns true if |bulkTransfer| should be used in |send|.
228 * See comments in |send|.
230 private boolean shouldUseBulkTransfer() {
231 return mHasInputThread;
235 * Returns the descriptors bytes of this device.
236 * @return The descriptors bytes of this device.
239 byte[] getDescriptors() {
240 if (mConnection == null) {
243 return mConnection.getRawDescriptors();
247 * Closes the device connection.
251 mEndpointMap.clear();
252 for (UsbRequest request : mRequestMap.values()) {
262 * Returns the length of a USB-MIDI input.
263 * Since the Android API doesn't provide us the length,
264 * we calculate it manually.
266 private static int getInputDataLength(ByteBuffer buffer) {
267 int position = buffer.position();
268 // We assume that the data length is always divisable by 4.
269 for (int i = 0; i < position; i += 4) {
270 // Since Code Index Number 0 is reserved, it is not a valid USB-MIDI data.
271 if (buffer.get(i) == 0) {
278 private static native void nativeOnData(long nativeUsbMidiDeviceAndroid,