Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / media / base / android / java / src / org / chromium / media / UsbMidiDeviceAndroid.java
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;
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.CalledByNative;
20 import org.chromium.base.JNINamespace;
21
22 import java.nio.ByteBuffer;
23 import java.util.HashMap;
24 import java.util.Map;
25
26 /**
27  * Owned by its native counterpart declared in usb_midi_device_android.h.
28  * Refer to that class for general comments.
29  */
30 @JNINamespace("media")
31 class UsbMidiDeviceAndroid {
32     /**
33      * A connection handle for this device.
34      */
35     private final UsbDeviceConnection mConnection;
36
37     /**
38      * A map from endpoint number to UsbEndpoint.
39      */
40     private final SparseArray<UsbEndpoint> mEndpointMap;
41
42     /**
43      * A map from UsbEndpoint to UsbRequest associated to it.
44      */
45     private final Map<UsbEndpoint, UsbRequest> mRequestMap;
46
47     /**
48      * The handler used for posting events on the main thread.
49      */
50     private final Handler mHandler;
51
52     /**
53      * True if this device is closed.
54      */
55     private boolean mIsClosed;
56
57     /**
58      * True if there is a thread processing input data.
59      */
60     private boolean mHasInputThread;
61
62     /**
63      * The identifier of this device.
64      */
65     private long mNativePointer;
66
67     /**
68      * Audio interface subclass code for MIDI.
69      */
70     static final int MIDI_SUBCLASS = 3;
71
72     /**
73      * Constructs a UsbMidiDeviceAndroid.
74      * @param manager
75      * @param device The USB device which this object is assocated with.
76      */
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();
82         mIsClosed = false;
83         mHasInputThread = false;
84         mNativePointer = 0;
85
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) {
90                 continue;
91             }
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);
97                 }
98             }
99         }
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.
105         startListen(device);
106     }
107
108     /**
109      * Starts listening for input endpoints.
110      */
111     private void startListen(final UsbDevice device) {
112         final Map<UsbEndpoint, ByteBuffer> bufferForEndpoints =
113                 new HashMap<UsbEndpoint, ByteBuffer>();
114
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) {
119                 continue;
120             }
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);
129                 }
130             }
131         }
132         if (bufferForEndpoints.isEmpty()) {
133             return;
134         }
135         mHasInputThread = true;
136         // bufferForEndpoints must not be accessed hereafter on this thread.
137         new Thread() {
138             @Override
139             public void run() {
140                 while (true) {
141                     UsbRequest request = mConnection.requestWait();
142                     if (request == null) {
143                         // When the device is closed requestWait will fail.
144                         break;
145                     }
146                     UsbEndpoint endpoint = request.getEndpoint();
147                     if (endpoint.getDirection() != UsbConstants.USB_DIR_IN) {
148                         continue;
149                     }
150                     ByteBuffer buffer = bufferForEndpoints.get(endpoint);
151                     int length = getInputDataLength(buffer);
152                     if (length > 0) {
153                         buffer.rewind();
154                         final byte[] bs = new byte[length];
155                         buffer.get(bs, 0, length);
156                         postOnDataEvent(endpoint.getEndpointNumber(), bs);
157                     }
158                     buffer.rewind();
159                     request.queue(buffer, buffer.capacity());
160                 }
161             }
162         }.start();
163     }
164
165     /**
166      * Posts a data input event to the main thread.
167      */
168     private void postOnDataEvent(final int endpointNumber, final byte[] bs) {
169         mHandler.post(new Runnable() {
170                 @Override
171                 public void run() {
172                     if (mIsClosed) {
173                         return;
174                     }
175                     nativeOnData(mNativePointer, endpointNumber, bs);
176                 }
177             });
178     }
179
180     /**
181      * Register the own native pointer.
182      */
183     @CalledByNative
184     void registerSelf(long nativePointer) {
185         mNativePointer = nativePointer;
186     }
187
188     /**
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.
192      */
193     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
194     @CalledByNative
195     void send(int endpointNumber, byte[] bs) {
196         if (mIsClosed) {
197             return;
198         }
199         UsbEndpoint endpoint = mEndpointMap.get(endpointNumber);
200         if (endpoint == null) {
201             return;
202         }
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.
208             // See also:
209             //  http://stackoverflow.com/questions/9644415/
210             //  https://code.google.com/p/android/issues/detail?id=59467
211             //
212             // TODO(yhirano): Delete this block once the problem is fixed.
213             final int timeout = 100;
214             mConnection.bulkTransfer(endpoint, bs, bs.length, timeout);
215         } else {
216             UsbRequest request = mRequestMap.get(endpoint);
217             if (request == null) {
218                 request = new UsbRequest();
219                 request.initialize(mConnection, endpoint);
220                 mRequestMap.put(endpoint, request);
221             }
222             request.queue(ByteBuffer.wrap(bs), bs.length);
223         }
224     }
225
226     /**
227      * Returns true if |bulkTransfer| should be used in |send|.
228      * See comments in |send|.
229      */
230     private boolean shouldUseBulkTransfer() {
231         return mHasInputThread;
232     }
233
234     /**
235      * Returns the descriptors bytes of this device.
236      * @return The descriptors bytes of this device.
237      */
238     @CalledByNative
239     byte[] getDescriptors() {
240         if (mConnection == null) {
241             return new byte[0];
242         }
243         return mConnection.getRawDescriptors();
244     }
245
246     /**
247      * Closes the device connection.
248      */
249     @CalledByNative
250     void close() {
251         mEndpointMap.clear();
252         for (UsbRequest request : mRequestMap.values()) {
253             request.close();
254         }
255         mRequestMap.clear();
256         mConnection.close();
257         mNativePointer = 0;
258         mIsClosed = true;
259     }
260
261     /**
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.
265      */
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) {
272                 return i;
273             }
274         }
275         return position;
276     }
277
278     private static native void nativeOnData(long nativeUsbMidiDeviceAndroid,
279                                             int endpointNumber,
280                                             byte[] data);
281 }