Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(291)

Side by Side Diff: media/midi/java/src/org/chromium/media/midi/UsbMidiDeviceAndroid.java

Issue 2418493002: //media/midi: use top level namespace midi rather than media.midi (Closed)
Patch Set: TAG name change s/media_midi/midi/ Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698