1 /*
2  * Copyright (C) 2021 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.server.usb;
18 
19 import android.annotation.NonNull;
20 import android.content.Context;
21 import android.hardware.usb.UsbConfiguration;
22 import android.hardware.usb.UsbDevice;
23 import android.hardware.usb.UsbDeviceConnection;
24 import android.hardware.usb.UsbEndpoint;
25 import android.hardware.usb.UsbInterface;
26 import android.hardware.usb.UsbManager;
27 import android.hardware.usb.UsbRequest;
28 import android.media.midi.MidiDeviceInfo;
29 import android.media.midi.MidiDeviceServer;
30 import android.media.midi.MidiDeviceStatus;
31 import android.media.midi.MidiManager;
32 import android.media.midi.MidiReceiver;
33 import android.os.Bundle;
34 import android.service.usb.UsbDirectMidiDeviceProto;
35 import android.util.Log;
36 
37 import com.android.internal.midi.MidiEventMultiScheduler;
38 import com.android.internal.midi.MidiEventScheduler;
39 import com.android.internal.midi.MidiEventScheduler.MidiEvent;
40 import com.android.internal.util.dump.DualDumpOutputStream;
41 import com.android.server.usb.descriptors.UsbACMidi10Endpoint;
42 import com.android.server.usb.descriptors.UsbDescriptor;
43 import com.android.server.usb.descriptors.UsbDescriptorParser;
44 import com.android.server.usb.descriptors.UsbEndpointDescriptor;
45 import com.android.server.usb.descriptors.UsbInterfaceDescriptor;
46 import com.android.server.usb.descriptors.UsbMidiBlockParser;
47 
48 import libcore.io.IoUtils;
49 
50 import java.io.ByteArrayOutputStream;
51 import java.io.Closeable;
52 import java.io.IOException;
53 import java.nio.ByteBuffer;
54 import java.util.ArrayList;
55 
56 /**
57  * Opens device connections to MIDI 1.0 or MIDI 2.0 endpoints.
58  * This endpoint will not use ALSA and opens a UsbDeviceConnection directly.
59  */
60 public final class UsbDirectMidiDevice implements Closeable {
61     private static final String TAG = "UsbDirectMidiDevice";
62     private static final boolean DEBUG = true;
63 
64     private Context mContext;
65     private String mName;
66     private UsbDevice mUsbDevice;
67     private UsbDescriptorParser mParser;
68     private ArrayList<UsbInterfaceDescriptor> mUsbInterfaces;
69     private final boolean mIsUniversalMidiDevice;
70     private final String mUniqueUsbDeviceIdentifier;
71     private final boolean mShouldCallSetInterface;
72 
73     // USB outputs are MIDI inputs
74     private final InputReceiverProxy[] mMidiInputPortReceivers;
75     private final int mNumInputs;
76     private final int mNumOutputs;
77 
78     private MidiDeviceServer mServer;
79 
80     // Timeout for sending a packet to a device.
81     // If bulkTransfer times out, retry sending the packet up to 20 times.
82     private static final int BULK_TRANSFER_TIMEOUT_MILLISECONDS = 50;
83     private static final int BULK_TRANSFER_NUMBER_OF_RETRIES = 20;
84 
85     // Arbitrary number for timeout when closing a thread
86     private static final int THREAD_JOIN_TIMEOUT_MILLISECONDS = 200;
87 
88     private ArrayList<UsbDeviceConnection> mUsbDeviceConnections;
89 
90     // Array of endpoints by device connection.
91     private ArrayList<ArrayList<UsbEndpoint>> mInputUsbEndpoints;
92     private ArrayList<ArrayList<UsbEndpoint>> mOutputUsbEndpoints;
93 
94     // Array of cable counts by device connection.
95     // Each number here maps to an entry in mInputUsbEndpoints or mOutputUsbEndpoints.
96     // This is needed because this info is part of UsbEndpointDescriptor but not UsbEndpoint.
97     private ArrayList<ArrayList<Integer>> mInputUsbEndpointCableCounts;
98     private ArrayList<ArrayList<Integer>> mOutputUsbEndpointCableCounts;
99 
100     // Array of event schedulers by device connection.
101     // Each number here maps to an entry in mOutputUsbEndpoints.
102     private ArrayList<ArrayList<MidiEventMultiScheduler>> mMidiEventMultiSchedulers;
103 
104     private ArrayList<Thread> mThreads;
105 
106     private UsbMidiBlockParser mMidiBlockParser = new UsbMidiBlockParser();
107     private int mDefaultMidiProtocol = MidiDeviceInfo.PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS;
108 
109     private final Object mLock = new Object();
110     private boolean mIsOpen;
111     private boolean mServerAvailable;
112 
113     private PowerBoostSetter mPowerBoostSetter = null;
114 
115     private static final byte MESSAGE_TYPE_MIDI_1_CHANNEL_VOICE = 0x02;
116     private static final byte MESSAGE_TYPE_MIDI_2_CHANNEL_VOICE = 0x04;
117 
118     private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() {
119         @Override
120         public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) {
121             MidiDeviceInfo deviceInfo = status.getDeviceInfo();
122             int numInputPorts = deviceInfo.getInputPortCount();
123             int numOutputPorts = deviceInfo.getOutputPortCount();
124             int numOpenPorts = 0;
125 
126             for (int i = 0; i < numInputPorts; i++) {
127                 if (status.isInputPortOpen(i)) {
128                     numOpenPorts++;
129                 }
130             }
131 
132             for (int i = 0; i < numOutputPorts; i++) {
133                 if (status.getOutputPortOpenCount(i) > 0) {
134                     numOpenPorts += status.getOutputPortOpenCount(i);
135                 }
136             }
137 
138             synchronized (mLock) {
139                 Log.d(TAG, "numOpenPorts: " + numOpenPorts + " isOpen: " + mIsOpen
140                         + " mServerAvailable: " + mServerAvailable);
141                 if ((numOpenPorts > 0) && !mIsOpen && mServerAvailable) {
142                     openLocked();
143                 } else if ((numOpenPorts == 0) && mIsOpen) {
144                     closeLocked();
145                 }
146             }
147         }
148 
149         @Override
150         public void onClose() {
151         }
152     };
153 
154     // This class acts as a proxy for our MidiEventScheduler receivers, which do not exist
155     // until the device has active clients
156     private static final class InputReceiverProxy extends MidiReceiver {
157         private MidiReceiver mReceiver;
158 
159         @Override
onSend(byte[] msg, int offset, int count, long timestamp)160         public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException {
161             MidiReceiver receiver = mReceiver;
162             if (receiver != null) {
163                 receiver.send(msg, offset, count, timestamp);
164             }
165         }
166 
setReceiver(MidiReceiver receiver)167         public void setReceiver(MidiReceiver receiver) {
168             mReceiver = receiver;
169         }
170 
171         @Override
onFlush()172         public void onFlush() throws IOException {
173             MidiReceiver receiver = mReceiver;
174             if (receiver != null) {
175                 receiver.flush();
176             }
177         }
178     }
179 
180     /**
181      * Creates an UsbDirectMidiDevice based on the input parameters. Read/Write streams
182      * will be created individually as some devices don't have the same number of
183      * inputs and outputs.
184      */
create(Context context, UsbDevice usbDevice, UsbDescriptorParser parser, boolean isUniversalMidiDevice, String uniqueUsbDeviceIdentifier)185     public static UsbDirectMidiDevice create(Context context, UsbDevice usbDevice,
186             UsbDescriptorParser parser, boolean isUniversalMidiDevice,
187             String uniqueUsbDeviceIdentifier) {
188         UsbDirectMidiDevice midiDevice = new UsbDirectMidiDevice(usbDevice, parser,
189                 isUniversalMidiDevice, uniqueUsbDeviceIdentifier);
190         if (!midiDevice.register(context)) {
191             IoUtils.closeQuietly(midiDevice);
192             Log.e(TAG, "createDeviceServer failed");
193             return null;
194         }
195         return midiDevice;
196     }
197 
UsbDirectMidiDevice(UsbDevice usbDevice, UsbDescriptorParser parser, boolean isUniversalMidiDevice, String uniqueUsbDeviceIdentifier)198     private UsbDirectMidiDevice(UsbDevice usbDevice, UsbDescriptorParser parser,
199             boolean isUniversalMidiDevice, String uniqueUsbDeviceIdentifier) {
200         mUsbDevice = usbDevice;
201         mParser = parser;
202         mUniqueUsbDeviceIdentifier = uniqueUsbDeviceIdentifier;
203         mIsUniversalMidiDevice = isUniversalMidiDevice;
204 
205         // Set interface should only be called when alternate interfaces exist.
206         // Otherwise, USB devices may not handle this gracefully.
207         mShouldCallSetInterface = (parser.calculateMidiInterfaceDescriptorsCount() > 1);
208 
209         ArrayList<UsbInterfaceDescriptor> midiInterfaceDescriptors;
210         if (isUniversalMidiDevice) {
211             midiInterfaceDescriptors = parser.findUniversalMidiInterfaceDescriptors();
212         } else {
213             midiInterfaceDescriptors = parser.findLegacyMidiInterfaceDescriptors();
214         }
215 
216         mUsbInterfaces = new ArrayList<UsbInterfaceDescriptor>();
217         if (mUsbDevice.getConfigurationCount() > 0) {
218             // USB devices should default to the first configuration.
219             // The first configuration should support MIDI.
220             // Only one configuration can be used at once.
221             // Thus, use USB interfaces from the first configuration.
222             UsbConfiguration usbConfiguration = mUsbDevice.getConfiguration(0);
223             for (int interfaceIndex = 0; interfaceIndex < usbConfiguration.getInterfaceCount();
224                     interfaceIndex++) {
225                 UsbInterface usbInterface = usbConfiguration.getInterface(interfaceIndex);
226                 for (UsbInterfaceDescriptor midiInterfaceDescriptor : midiInterfaceDescriptors) {
227                     UsbInterface midiInterface = midiInterfaceDescriptor.toAndroid(mParser);
228                     if (areEquivalent(usbInterface, midiInterface)) {
229                         mUsbInterfaces.add(midiInterfaceDescriptor);
230                         break;
231                     }
232                 }
233             }
234 
235             if (mUsbDevice.getConfigurationCount() > 1) {
236                 Log.w(TAG, "Skipping some USB configurations. Count: "
237                         + mUsbDevice.getConfigurationCount());
238             }
239         }
240 
241         int numInputs = 0;
242         int numOutputs = 0;
243 
244         for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) {
245             UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex);
246             for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints();
247                     endpointIndex++) {
248                 UsbEndpointDescriptor endpoint =
249                         interfaceDescriptor.getEndpointDescriptor(endpointIndex);
250                 // 0 is output, 1 << 7 is input.
251                 if (endpoint.getDirection() == 0) {
252                     numOutputs += getNumJacks(endpoint);
253                 } else {
254                     numInputs += getNumJacks(endpoint);
255                 }
256             }
257         }
258 
259         mNumInputs = numInputs;
260         mNumOutputs = numOutputs;
261 
262         Log.d(TAG, "Created UsbDirectMidiDevice with " + numInputs + " inputs and "
263                 + numOutputs + " outputs. isUniversalMidiDevice: " + isUniversalMidiDevice);
264 
265         // Create MIDI port receivers based on the number of output ports. The
266         // output of USB is the input of MIDI.
267         mMidiInputPortReceivers = new InputReceiverProxy[numOutputs];
268         for (int port = 0; port < numOutputs; port++) {
269             mMidiInputPortReceivers[port] = new InputReceiverProxy();
270         }
271 
272         mPowerBoostSetter = new PowerBoostSetter();
273     }
274 
calculateDefaultMidiProtocol()275     private int calculateDefaultMidiProtocol() {
276         UsbManager manager = mContext.getSystemService(UsbManager.class);
277 
278         for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) {
279             UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex);
280             boolean doesInterfaceContainInput = false;
281             boolean doesInterfaceContainOutput = false;
282             for (int endpointIndex = 0; (endpointIndex < interfaceDescriptor.getNumEndpoints())
283                     && !(doesInterfaceContainInput && doesInterfaceContainOutput);
284                     endpointIndex++) {
285                 UsbEndpointDescriptor endpoint =
286                         interfaceDescriptor.getEndpointDescriptor(endpointIndex);
287                 // 0 is output, 1 << 7 is input.
288                 if (endpoint.getDirection() == 0) {
289                     doesInterfaceContainOutput = true;
290                 } else {
291                     doesInterfaceContainInput = true;
292                 }
293             }
294 
295             // Intentionally open the device connection to query the default MIDI type for
296             // a connection with both the input and output set.
297             if (doesInterfaceContainInput
298                     && doesInterfaceContainOutput) {
299                 UsbDeviceConnection connection = manager.openDevice(mUsbDevice);
300                 UsbInterface usbInterface = interfaceDescriptor.toAndroid(mParser);
301                 if (!updateUsbInterface(usbInterface, connection)) {
302                     continue;
303                 }
304                 int defaultMidiProtocol = mMidiBlockParser.calculateMidiType(connection,
305                         interfaceDescriptor.getInterfaceNumber(),
306                         interfaceDescriptor.getAlternateSetting());
307 
308                 connection.close();
309                 return defaultMidiProtocol;
310             }
311         }
312 
313         Log.w(TAG, "Cannot find interface with both input and output endpoints");
314         return MidiDeviceInfo.PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS;
315     }
316 
openLocked()317     private boolean openLocked() {
318         Log.d(TAG, "openLocked()");
319         UsbManager manager = mContext.getSystemService(UsbManager.class);
320 
321         mUsbDeviceConnections = new ArrayList<UsbDeviceConnection>();
322         mInputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>();
323         mOutputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>();
324         mInputUsbEndpointCableCounts = new ArrayList<ArrayList<Integer>>();
325         mOutputUsbEndpointCableCounts = new ArrayList<ArrayList<Integer>>();
326         mMidiEventMultiSchedulers = new ArrayList<ArrayList<MidiEventMultiScheduler>>();
327         mThreads = new ArrayList<Thread>();
328 
329         for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) {
330             ArrayList<UsbEndpoint> inputEndpoints = new ArrayList<UsbEndpoint>();
331             ArrayList<UsbEndpoint> outputEndpoints = new ArrayList<UsbEndpoint>();
332             ArrayList<Integer> inputEndpointCableCounts = new ArrayList<Integer>();
333             ArrayList<Integer> outputEndpointCableCounts = new ArrayList<Integer>();
334             ArrayList<MidiEventMultiScheduler> midiEventMultiSchedulers =
335                     new ArrayList<MidiEventMultiScheduler>();
336             UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex);
337             for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints();
338                     endpointIndex++) {
339                 UsbEndpointDescriptor endpoint =
340                         interfaceDescriptor.getEndpointDescriptor(endpointIndex);
341                 // 0 is output, 1 << 7 is input.
342                 if (endpoint.getDirection() == 0) {
343                     outputEndpoints.add(endpoint.toAndroid(mParser));
344                     outputEndpointCableCounts.add(getNumJacks(endpoint));
345                     MidiEventMultiScheduler scheduler =
346                             new MidiEventMultiScheduler(getNumJacks(endpoint));
347                     midiEventMultiSchedulers.add(scheduler);
348                 } else {
349                     inputEndpoints.add(endpoint.toAndroid(mParser));
350                     inputEndpointCableCounts.add(getNumJacks(endpoint));
351                 }
352             }
353             if (!outputEndpoints.isEmpty() || !inputEndpoints.isEmpty()) {
354                 UsbDeviceConnection connection = manager.openDevice(mUsbDevice);
355                 UsbInterface usbInterface = interfaceDescriptor.toAndroid(mParser);
356                 if (!updateUsbInterface(usbInterface, connection)) {
357                     continue;
358                 }
359                 mUsbDeviceConnections.add(connection);
360                 mInputUsbEndpoints.add(inputEndpoints);
361                 mOutputUsbEndpoints.add(outputEndpoints);
362                 mInputUsbEndpointCableCounts.add(inputEndpointCableCounts);
363                 mOutputUsbEndpointCableCounts.add(outputEndpointCableCounts);
364                 mMidiEventMultiSchedulers.add(midiEventMultiSchedulers);
365             }
366         }
367 
368         // Set up event schedulers
369         int outputIndex = 0;
370         for (int connectionIndex = 0; connectionIndex < mMidiEventMultiSchedulers.size();
371                 connectionIndex++) {
372             for (int endpointIndex = 0;
373                     endpointIndex < mMidiEventMultiSchedulers.get(connectionIndex).size();
374                     endpointIndex++) {
375                 int cableCount =
376                         mOutputUsbEndpointCableCounts.get(connectionIndex).get(endpointIndex);
377                 MidiEventMultiScheduler multiScheduler =
378                         mMidiEventMultiSchedulers.get(connectionIndex).get(endpointIndex);
379                 for (int cableNumber = 0; cableNumber < cableCount; cableNumber++) {
380                     MidiEventScheduler scheduler = multiScheduler.getEventScheduler(cableNumber);
381                     mMidiInputPortReceivers[outputIndex].setReceiver(scheduler.getReceiver());
382                     outputIndex++;
383                 }
384             }
385         }
386 
387         final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
388 
389         // Create input thread for each input port of the physical device
390         int portStartNumber = 0;
391         for (int connectionIndex = 0; connectionIndex < mInputUsbEndpoints.size();
392                 connectionIndex++) {
393             for (int endpointIndex = 0;
394                     endpointIndex < mInputUsbEndpoints.get(connectionIndex).size();
395                     endpointIndex++) {
396                 // Each USB endpoint maps to one or more outputReceivers. USB MIDI data from an
397                 // endpoint should be sent to the appropriate outputReceiver. A new thread is
398                 // created and waits for incoming USB data. Once the data is received, it is added
399                 // to the packet converter. The packet converter acts as an inverse multiplexer.
400                 // With a for loop, data is pulled per cable and sent to the correct output
401                 // receiver. The first byte of each legacy MIDI 1.0 USB message indicates which
402                 // cable the data should be used and is how the reverse multiplexer directs data.
403                 // For MIDI UMP endpoints, a multiplexer is not needed as we are just swapping
404                 // the endianness of the packets.
405                 final UsbDeviceConnection connectionFinal =
406                         mUsbDeviceConnections.get(connectionIndex);
407                 final UsbEndpoint endpointFinal =
408                         mInputUsbEndpoints.get(connectionIndex).get(endpointIndex);
409                 final int portStartFinal = portStartNumber;
410                 final int cableCountFinal =
411                         mInputUsbEndpointCableCounts.get(connectionIndex).get(endpointIndex);
412 
413                 Thread newThread = new Thread("UsbDirectMidiDevice input thread "
414                         + portStartFinal) {
415                     @Override
416                     public void run() {
417                         final UsbRequest request = new UsbRequest();
418                         final UsbMidiPacketConverter packetConverter = new UsbMidiPacketConverter();
419                         packetConverter.createDecoders(cableCountFinal);
420                         try {
421                             request.initialize(connectionFinal, endpointFinal);
422                             byte[] inputBuffer = new byte[endpointFinal.getMaxPacketSize()];
423                             boolean keepGoing = true;
424                             while (keepGoing) {
425                                 if (Thread.currentThread().interrupted()) {
426                                     Log.w(TAG, "input thread interrupted");
427                                     break;
428                                 }
429                                 final ByteBuffer byteBuffer = ByteBuffer.wrap(inputBuffer);
430                                 if (!request.queue(byteBuffer)) {
431                                     Log.w(TAG, "Cannot queue request");
432                                     break;
433                                 }
434                                 final UsbRequest response = connectionFinal.requestWait();
435                                 if (response == null) {
436                                     Log.w(TAG, "Response is null");
437                                     break;
438                                 }
439                                 if (request != response) {
440                                     Log.w(TAG, "Skipping response");
441                                     continue;
442                                 }
443 
444                                 // Record time of event after receiving response.
445                                 long timestamp = System.nanoTime();
446 
447                                 int bytesRead = byteBuffer.position();
448 
449                                 if (bytesRead > 0) {
450                                     if (DEBUG) {
451                                         logByteArray("Input before conversion ", inputBuffer,
452                                                 0, bytesRead);
453                                     }
454 
455                                     // Add packets into the packet decoder.
456                                     if (!mIsUniversalMidiDevice) {
457                                         packetConverter.decodeMidiPackets(inputBuffer, bytesRead);
458                                     }
459 
460                                     byte[] convertedArray;
461                                     for (int cableNumber = 0; cableNumber < cableCountFinal;
462                                             cableNumber++) {
463                                         if (mIsUniversalMidiDevice) {
464                                             // For USB, each 32 bit word of a UMP is
465                                             // sent with the least significant byte first.
466                                             convertedArray = swapEndiannessPerWord(inputBuffer,
467                                                     bytesRead);
468                                         } else {
469                                             convertedArray =
470                                                     packetConverter.pullDecodedMidiPackets(
471                                                             cableNumber);
472                                         }
473 
474                                         if (DEBUG) {
475                                             logByteArray("Input " + cableNumber
476                                                     + " after conversion ", convertedArray, 0,
477                                                     convertedArray.length);
478                                         }
479 
480                                         if (convertedArray.length == 0) {
481                                             continue;
482                                         }
483 
484                                         if ((outputReceivers == null)
485                                                 || (outputReceivers[portStartFinal + cableNumber]
486                                                 == null)) {
487                                             Log.w(TAG, "outputReceivers is null");
488                                             keepGoing = false;
489                                             break;
490                                         }
491                                         outputReceivers[portStartFinal + cableNumber].send(
492                                                 convertedArray, 0, convertedArray.length,
493                                                 timestamp);
494 
495                                         // Boost power if there seems to be a voice message.
496                                         // For legacy devices, boost if message length > 1.
497                                         // For UMP devices, boost for channel voice messages.
498                                         if ((mPowerBoostSetter != null
499                                                 && convertedArray.length > 1)
500                                                 && (!mIsUniversalMidiDevice
501                                                 || isChannelVoiceMessage(convertedArray))) {
502                                             mPowerBoostSetter.boostPower();
503                                         }
504                                     }
505                                 }
506                             }
507                         } catch (IOException e) {
508                             Log.d(TAG, "reader thread exiting");
509                         } catch (NullPointerException e) {
510                             Log.e(TAG, "input thread: ", e);
511                         } finally {
512                             request.close();
513                         }
514                         Log.d(TAG, "input thread exit");
515                     }
516                 };
517                 newThread.start();
518                 mThreads.add(newThread);
519                 portStartNumber += cableCountFinal;
520             }
521         }
522 
523         // Create output thread for each output port of the physical device
524         portStartNumber = 0;
525         for (int connectionIndex = 0; connectionIndex < mOutputUsbEndpoints.size();
526                 connectionIndex++) {
527             for (int endpointIndex = 0;
528                     endpointIndex < mOutputUsbEndpoints.get(connectionIndex).size();
529                     endpointIndex++) {
530                 // Each USB endpoint maps to one or more MIDI ports. Each port has an event
531                 // scheduler that is used to pull incoming raw MIDI bytes from Android apps.
532                 // With a MidiEventMultiScheduler, data can be pulled if any of the schedulers
533                 // have new incoming data. This data is then packaged as USB MIDI packets before
534                 // getting sent through USB. One thread will be created per endpoint that pulls
535                 // data from all relevant event schedulers. Raw MIDI from the event schedulers
536                 // will be converted to the correct USB MIDI format before getting sent through
537                 // USB.
538 
539                 final UsbDeviceConnection connectionFinal =
540                         mUsbDeviceConnections.get(connectionIndex);
541                 final UsbEndpoint endpointFinal =
542                         mOutputUsbEndpoints.get(connectionIndex).get(endpointIndex);
543                 final int portStartFinal = portStartNumber;
544                 final int cableCountFinal =
545                         mOutputUsbEndpointCableCounts.get(connectionIndex).get(endpointIndex);
546                 final MidiEventMultiScheduler multiSchedulerFinal =
547                             mMidiEventMultiSchedulers.get(connectionIndex).get(endpointIndex);
548 
549                 Thread newThread = new Thread("UsbDirectMidiDevice output write thread "
550                         + portStartFinal) {
551                     @Override
552                     public void run() {
553                         try {
554                             final ByteArrayOutputStream midi2ByteStream =
555                                     new ByteArrayOutputStream();
556                             final UsbMidiPacketConverter packetConverter =
557                                     new UsbMidiPacketConverter();
558                             packetConverter.createEncoders(cableCountFinal);
559                             boolean isInterrupted = false;
560                             while (!isInterrupted) {
561                                 boolean wasSuccessful = multiSchedulerFinal.waitNextEvent();
562                                 if (!wasSuccessful) {
563                                     Log.d(TAG, "output thread closed");
564                                     break;
565                                 }
566                                 long now = System.nanoTime();
567                                 for (int cableNumber = 0; cableNumber < cableCountFinal;
568                                         cableNumber++) {
569                                     MidiEventScheduler eventScheduler =
570                                             multiSchedulerFinal.getEventScheduler(cableNumber);
571                                     MidiEvent event =
572                                             (MidiEvent) eventScheduler.getNextEvent(now);
573                                     while (event != null) {
574                                         if (DEBUG) {
575                                             logByteArray("Output before conversion ",
576                                                     event.data, 0, event.count);
577                                         }
578                                         if (mIsUniversalMidiDevice) {
579                                             // For USB, each 32 bit word of a UMP is
580                                             // sent with the least significant byte first.
581                                             byte[] convertedArray = swapEndiannessPerWord(
582                                                     event.data, event.count);
583                                             midi2ByteStream.write(convertedArray, 0,
584                                                     convertedArray.length);
585                                         } else {
586                                             packetConverter.encodeMidiPackets(event.data,
587                                                     event.count, cableNumber);
588                                         }
589                                         eventScheduler.addEventToPool(event);
590                                         event = (MidiEvent) eventScheduler.getNextEvent(now);
591                                     }
592                                 }
593 
594                                 if (Thread.currentThread().interrupted()) {
595                                     Log.d(TAG, "output thread interrupted");
596                                     break;
597                                 }
598 
599                                 byte[] convertedArray = new byte[0];
600                                 if (mIsUniversalMidiDevice) {
601                                     convertedArray = midi2ByteStream.toByteArray();
602                                     midi2ByteStream.reset();
603                                 } else {
604                                     convertedArray =
605                                             packetConverter.pullEncodedMidiPackets();
606                                 }
607 
608                                 if (DEBUG) {
609                                     logByteArray("Output after conversion ", convertedArray, 0,
610                                             convertedArray.length);
611                                 }
612 
613                                 // Split the packet into multiple if they are greater than the
614                                 // endpoint's max packet size.
615                                 for (int curPacketStart = 0;
616                                         curPacketStart < convertedArray.length &&
617                                         isInterrupted == false;
618                                         curPacketStart += endpointFinal.getMaxPacketSize()) {
619                                     int transferResult = -1;
620                                     int retryCount = 0;
621                                     int curPacketSize = Math.min(endpointFinal.getMaxPacketSize(),
622                                             convertedArray.length - curPacketStart);
623 
624                                     // Keep trying to send the packet until the result is
625                                     // successful or until the retry limit is reached.
626                                     while (transferResult < 0 && retryCount <=
627                                             BULK_TRANSFER_NUMBER_OF_RETRIES) {
628                                         transferResult = connectionFinal.bulkTransfer(
629                                                 endpointFinal,
630                                                 convertedArray,
631                                                 curPacketStart,
632                                                 curPacketSize,
633                                                 BULK_TRANSFER_TIMEOUT_MILLISECONDS);
634                                         retryCount++;
635 
636                                         if (Thread.currentThread().interrupted()) {
637                                             Log.w(TAG, "output thread interrupted after send");
638                                             isInterrupted = true;
639                                             break;
640                                         }
641                                         if (transferResult < 0) {
642                                             Log.d(TAG, "retrying packet. retryCount = "
643                                                     + retryCount + " result = " + transferResult);
644                                             if (retryCount > BULK_TRANSFER_NUMBER_OF_RETRIES) {
645                                                 Log.w(TAG, "Skipping packet because timeout");
646                                             }
647                                         }
648                                     }
649                                 }
650                             }
651                         } catch (InterruptedException e) {
652                             Log.w(TAG, "output thread: ", e);
653                         } catch (NullPointerException e) {
654                             Log.e(TAG, "output thread: ", e);
655                         }
656                         Log.d(TAG, "output thread exit");
657                     }
658                 };
659                 newThread.start();
660                 mThreads.add(newThread);
661                 portStartNumber += cableCountFinal;
662             }
663         }
664 
665         mIsOpen = true;
666         return true;
667     }
668 
register(Context context)669     private boolean register(Context context) {
670         mContext = context;
671         MidiManager midiManager = context.getSystemService(MidiManager.class);
672         if (midiManager == null) {
673             Log.e(TAG, "No MidiManager in UsbDirectMidiDevice.register()");
674             return false;
675         }
676 
677         if (mIsUniversalMidiDevice) {
678             mDefaultMidiProtocol = calculateDefaultMidiProtocol();
679         } else {
680             mDefaultMidiProtocol = MidiDeviceInfo.PROTOCOL_UNKNOWN;
681         }
682 
683         Bundle properties = new Bundle();
684         String manufacturer = mUsbDevice.getManufacturerName();
685         String product = mUsbDevice.getProductName();
686         String version = mUsbDevice.getVersion();
687         String name;
688         if (manufacturer == null || manufacturer.isEmpty()) {
689             name = product;
690         } else if (product == null || product.isEmpty()) {
691             name = manufacturer;
692         } else {
693             name = manufacturer + " " + product;
694         }
695         name += "#" + mUniqueUsbDeviceIdentifier;
696         if (mIsUniversalMidiDevice) {
697             name += " MIDI 2.0";
698         } else {
699             name += " MIDI 1.0";
700         }
701         mName = name;
702         properties.putString(MidiDeviceInfo.PROPERTY_NAME, name);
703         properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer);
704         properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product);
705         properties.putString(MidiDeviceInfo.PROPERTY_VERSION, version);
706         properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER,
707                 mUsbDevice.getSerialNumber());
708         properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, mUsbDevice);
709 
710         mServerAvailable = true;
711         mServer = midiManager.createDeviceServer(mMidiInputPortReceivers, mNumInputs,
712                 null, null, properties, MidiDeviceInfo.TYPE_USB, mDefaultMidiProtocol, mCallback);
713         if (mServer == null) {
714             return false;
715         }
716 
717         return true;
718     }
719 
720     @Override
close()721     public void close() throws IOException {
722         synchronized (mLock) {
723             if (mIsOpen) {
724                 closeLocked();
725             }
726             mServerAvailable = false;
727         }
728 
729         if (mServer != null) {
730             IoUtils.closeQuietly(mServer);
731         }
732     }
733 
closeLocked()734     private void closeLocked() {
735         Log.d(TAG, "closeLocked()");
736 
737         // Send an interrupt signal to threads.
738         for (Thread thread : mThreads) {
739             if (thread != null) {
740                 thread.interrupt();
741             }
742         }
743 
744         // Wait for threads to actually stop.
745         for (Thread thread : mThreads) {
746             if (thread != null) {
747                 try {
748                     thread.join(THREAD_JOIN_TIMEOUT_MILLISECONDS);
749                 } catch (InterruptedException e) {
750                     Log.w(TAG, "thread join interrupted");
751                     break;
752                 }
753             }
754         }
755         mThreads = null;
756 
757         for (int i = 0; i < mMidiInputPortReceivers.length; i++) {
758             mMidiInputPortReceivers[i].setReceiver(null);
759         }
760 
761         for (int connectionIndex = 0; connectionIndex < mMidiEventMultiSchedulers.size();
762                 connectionIndex++) {
763             for (int endpointIndex = 0;
764                     endpointIndex < mMidiEventMultiSchedulers.get(connectionIndex).size();
765                     endpointIndex++) {
766                 MidiEventMultiScheduler multiScheduler =
767                         mMidiEventMultiSchedulers.get(connectionIndex).get(endpointIndex);
768                 multiScheduler.close();
769             }
770         }
771         mMidiEventMultiSchedulers = null;
772 
773         for (UsbDeviceConnection connection : mUsbDeviceConnections) {
774             connection.close();
775         }
776         mUsbDeviceConnections = null;
777         mInputUsbEndpoints = null;
778         mOutputUsbEndpoints = null;
779         mInputUsbEndpointCableCounts = null;
780         mOutputUsbEndpointCableCounts = null;
781 
782         mIsOpen = false;
783     }
784 
swapEndiannessPerWord(byte[] inputArray, int size)785     private byte[] swapEndiannessPerWord(byte[] inputArray, int size) {
786         int numberOfExcessBytes = size & 3;
787         if (numberOfExcessBytes != 0) {
788             Log.e(TAG, "size not multiple of 4: " + size);
789         }
790         byte[] outputArray = new byte[size - numberOfExcessBytes];
791         for (int i = 0; i + 3 < size; i += 4) {
792             outputArray[i] = inputArray[i + 3];
793             outputArray[i + 1] = inputArray[i + 2];
794             outputArray[i + 2] = inputArray[i + 1];
795             outputArray[i + 3] = inputArray[i];
796         }
797         return outputArray;
798     }
799 
logByteArray(String prefix, byte[] value, int offset, int count)800     private static void logByteArray(String prefix, byte[] value, int offset, int count) {
801         StringBuilder builder = new StringBuilder(prefix);
802         for (int i = offset; i < offset + count; i++) {
803             builder.append(String.format("0x%02X", value[i]));
804             if (i != value.length - 1) {
805                 builder.append(", ");
806             }
807         }
808         Log.d(TAG, builder.toString());
809     }
810 
updateUsbInterface(UsbInterface usbInterface, UsbDeviceConnection connection)811     private boolean updateUsbInterface(UsbInterface usbInterface,
812             UsbDeviceConnection connection) {
813         if (usbInterface == null) {
814             Log.e(TAG, "Usb Interface is null");
815             return false;
816         }
817         if (connection == null) {
818             Log.e(TAG, "UsbDeviceConnection is null");
819             return false;
820         }
821         if (!connection.claimInterface(usbInterface, true)) {
822             Log.e(TAG, "Can't claim interface");
823             return false;
824         }
825         if (mShouldCallSetInterface) {
826             if (!connection.setInterface(usbInterface)) {
827                 Log.w(TAG, "Can't set interface");
828             }
829         } else {
830             Log.w(TAG, "no alternate interface");
831         }
832         return true;
833     }
834 
areEquivalent(UsbInterface interface1, UsbInterface interface2)835     private boolean areEquivalent(UsbInterface interface1, UsbInterface interface2) {
836         if ((interface1.getId() != interface2.getId())
837                 || (interface1.getAlternateSetting() != interface2.getAlternateSetting())
838                 || (interface1.getInterfaceClass() != interface2.getInterfaceClass())
839                 || (interface1.getInterfaceSubclass() != interface2.getInterfaceSubclass())
840                 || (interface1.getInterfaceProtocol() != interface2.getInterfaceProtocol())
841                 || (interface1.getEndpointCount() != interface2.getEndpointCount())) {
842             return false;
843         }
844 
845         if (interface1.getName() == null) {
846             if (interface2.getName() != null) {
847                 return false;
848             }
849         } else if (!(interface1.getName().equals(interface2.getName()))) {
850             return false;
851         }
852 
853         // Consider devices with the same endpoints but in a different order as different endpoints.
854         for (int i = 0; i < interface1.getEndpointCount(); i++) {
855             UsbEndpoint endpoint1 = interface1.getEndpoint(i);
856             UsbEndpoint endpoint2 = interface2.getEndpoint(i);
857             if ((endpoint1.getAddress() != endpoint2.getAddress())
858                     || (endpoint1.getAttributes() != endpoint2.getAttributes())
859                     || (endpoint1.getMaxPacketSize() != endpoint2.getMaxPacketSize())
860                     || (endpoint1.getInterval() != endpoint2.getInterval())) {
861                 return false;
862             }
863         }
864         return true;
865     }
866     /**
867      * Write a description of the device to a dump stream.
868      */
dump(@onNull DualDumpOutputStream dump, @NonNull String idName, long id)869     public void dump(@NonNull DualDumpOutputStream dump, @NonNull String idName, long id) {
870         long token = dump.start(idName, id);
871 
872         dump.write("num_inputs", UsbDirectMidiDeviceProto.NUM_INPUTS, mNumInputs);
873         dump.write("num_outputs", UsbDirectMidiDeviceProto.NUM_OUTPUTS, mNumOutputs);
874         dump.write("is_universal", UsbDirectMidiDeviceProto.IS_UNIVERSAL, mIsUniversalMidiDevice);
875         dump.write("name", UsbDirectMidiDeviceProto.NAME, mName);
876         if (mIsUniversalMidiDevice) {
877             mMidiBlockParser.dump(dump, "block_parser", UsbDirectMidiDeviceProto.BLOCK_PARSER);
878         }
879 
880         dump.end(token);
881     }
882 
isChannelVoiceMessage(byte[] umpMessage)883     private boolean isChannelVoiceMessage(byte[] umpMessage) {
884         byte messageType = (byte) ((umpMessage[0] >> 4) & 0x0f);
885         return messageType == MESSAGE_TYPE_MIDI_1_CHANNEL_VOICE
886                 || messageType == MESSAGE_TYPE_MIDI_2_CHANNEL_VOICE;
887     }
888 
889     // Returns the number of jacks for MIDI 1.0 endpoints.
890     // For MIDI 2.0 endpoints, this concept does not exist and each endpoint should be treated as
891     // one port.
getNumJacks(UsbEndpointDescriptor usbEndpointDescriptor)892     private int getNumJacks(UsbEndpointDescriptor usbEndpointDescriptor) {
893         UsbDescriptor classSpecificEndpointDescriptor =
894                 usbEndpointDescriptor.getClassSpecificEndpointDescriptor();
895         if (classSpecificEndpointDescriptor != null
896                 && (classSpecificEndpointDescriptor instanceof UsbACMidi10Endpoint)) {
897             UsbACMidi10Endpoint midiEndpoint =
898                     (UsbACMidi10Endpoint) classSpecificEndpointDescriptor;
899             return midiEndpoint.getNumJacks();
900         }
901         return 1;
902     }
903 }
904