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