1 /* 2 * Copyright (C) 2017 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 package com.android.server.usb.descriptors; 17 18 import android.hardware.usb.UsbDevice; 19 import android.util.Log; 20 21 import java.util.ArrayList; 22 23 /** 24 * @hide 25 * Class for parsing a binary stream of USB Descriptors. 26 */ 27 public final class UsbDescriptorParser { 28 private static final String TAG = "UsbDescriptorParser"; 29 public static final boolean DEBUG = false; 30 31 private final String mDeviceAddr; 32 33 private static final int MS_MIDI_1_0 = 0x0100; 34 private static final int MS_MIDI_2_0 = 0x0200; 35 36 // Descriptor Objects 37 private static final int DESCRIPTORS_ALLOC_SIZE = 128; 38 private final ArrayList<UsbDescriptor> mDescriptors; 39 40 private UsbDeviceDescriptor mDeviceDescriptor; 41 private UsbConfigDescriptor mCurConfigDescriptor; 42 private UsbInterfaceDescriptor mCurInterfaceDescriptor; 43 private UsbEndpointDescriptor mCurEndpointDescriptor; 44 45 // The AudioClass spec implemented by the AudioClass Interfaces 46 // This may well be different than the overall USB Spec. 47 // Obtained from the first AudioClass Header descriptor. 48 private int mACInterfacesSpec = UsbDeviceDescriptor.USBSPEC_1_0; 49 50 // The VideoClass spec implemented by the VideoClass Interfaces 51 // This may well be different than the overall USB Spec. 52 // Obtained from the first VidieoClass Header descriptor. 53 private int mVCInterfacesSpec = UsbDeviceDescriptor.USBSPEC_1_0; 54 55 /** 56 * Connect this parser to an existing set of already parsed descriptors. 57 * This is useful for reporting. 58 */ UsbDescriptorParser(String deviceAddr, ArrayList<UsbDescriptor> descriptors)59 public UsbDescriptorParser(String deviceAddr, ArrayList<UsbDescriptor> descriptors) { 60 mDeviceAddr = deviceAddr; 61 mDescriptors = descriptors; 62 //TODO some error checking here.... 63 mDeviceDescriptor = (UsbDeviceDescriptor) descriptors.get(0); 64 } 65 66 /** 67 * Connect this parser to an byte array containing unparsed (raw) device descriptors 68 * to be parsed (and parse them). Useful for parsing a stored descriptor buffer. 69 */ UsbDescriptorParser(String deviceAddr, byte[] rawDescriptors)70 public UsbDescriptorParser(String deviceAddr, byte[] rawDescriptors) { 71 mDeviceAddr = deviceAddr; 72 mDescriptors = new ArrayList<UsbDescriptor>(DESCRIPTORS_ALLOC_SIZE); 73 parseDescriptors(rawDescriptors); 74 } 75 getDeviceAddr()76 public String getDeviceAddr() { 77 return mDeviceAddr; 78 } 79 80 /** 81 * @return the USB Spec value associated with the Device descriptor for the 82 * descriptors stream being parsed. 83 * 84 * @throws IllegalArgumentException 85 */ getUsbSpec()86 public int getUsbSpec() { 87 if (mDeviceDescriptor != null) { 88 return mDeviceDescriptor.getSpec(); 89 } else { 90 throw new IllegalArgumentException(); 91 } 92 } 93 setACInterfaceSpec(int spec)94 public void setACInterfaceSpec(int spec) { 95 mACInterfacesSpec = spec; 96 } 97 getACInterfaceSpec()98 public int getACInterfaceSpec() { 99 return mACInterfacesSpec; 100 } 101 setVCInterfaceSpec(int spec)102 public void setVCInterfaceSpec(int spec) { 103 mVCInterfacesSpec = spec; 104 } 105 getVCInterfaceSpec()106 public int getVCInterfaceSpec() { 107 return mVCInterfacesSpec; 108 } 109 110 private class UsbDescriptorsStreamFormatException extends Exception { 111 String mMessage; UsbDescriptorsStreamFormatException(String message)112 UsbDescriptorsStreamFormatException(String message) { 113 mMessage = message; 114 } 115 toString()116 public String toString() { 117 return "Descriptor Stream Format Exception: " + mMessage; 118 } 119 } 120 121 /** 122 * The probability (as returned by getHeadsetProbability() at which we conclude 123 * the peripheral is a headset. 124 */ 125 private static final float IN_HEADSET_TRIGGER = 0.75f; 126 private static final float OUT_HEADSET_TRIGGER = 0.75f; 127 allocDescriptor(ByteStream stream)128 private UsbDescriptor allocDescriptor(ByteStream stream) 129 throws UsbDescriptorsStreamFormatException { 130 stream.resetReadCount(); 131 132 int length = stream.getUnsignedByte(); 133 byte type = stream.getByte(); 134 135 UsbDescriptor.logDescriptorName(type, length); 136 137 UsbDescriptor descriptor = null; 138 switch (type) { 139 /* 140 * Standard 141 */ 142 case UsbDescriptor.DESCRIPTORTYPE_DEVICE: 143 descriptor = mDeviceDescriptor = new UsbDeviceDescriptor(length, type); 144 break; 145 146 case UsbDescriptor.DESCRIPTORTYPE_CONFIG: 147 descriptor = mCurConfigDescriptor = new UsbConfigDescriptor(length, type); 148 if (mDeviceDescriptor != null) { 149 mDeviceDescriptor.addConfigDescriptor(mCurConfigDescriptor); 150 } else { 151 Log.e(TAG, "Config Descriptor found with no associated Device Descriptor!"); 152 throw new UsbDescriptorsStreamFormatException( 153 "Config Descriptor found with no associated Device Descriptor!"); 154 } 155 break; 156 157 case UsbDescriptor.DESCRIPTORTYPE_INTERFACE: 158 descriptor = mCurInterfaceDescriptor = new UsbInterfaceDescriptor(length, type); 159 if (mCurConfigDescriptor != null) { 160 mCurConfigDescriptor.addInterfaceDescriptor(mCurInterfaceDescriptor); 161 } else { 162 Log.e(TAG, "Interface Descriptor found with no associated Config Descriptor!"); 163 throw new UsbDescriptorsStreamFormatException( 164 "Interface Descriptor found with no associated Config Descriptor!"); 165 } 166 break; 167 168 case UsbDescriptor.DESCRIPTORTYPE_ENDPOINT: 169 descriptor = mCurEndpointDescriptor = new UsbEndpointDescriptor(length, type); 170 if (mCurInterfaceDescriptor != null) { 171 mCurInterfaceDescriptor.addEndpointDescriptor( 172 (UsbEndpointDescriptor) descriptor); 173 } else { 174 Log.e(TAG, 175 "Endpoint Descriptor found with no associated Interface Descriptor!"); 176 throw new UsbDescriptorsStreamFormatException( 177 "Endpoint Descriptor found with no associated Interface Descriptor!"); 178 } 179 break; 180 181 /* 182 * HID 183 */ 184 case UsbDescriptor.DESCRIPTORTYPE_HID: 185 descriptor = new UsbHIDDescriptor(length, type); 186 break; 187 188 /* 189 * Other 190 */ 191 case UsbDescriptor.DESCRIPTORTYPE_INTERFACEASSOC: 192 descriptor = new UsbInterfaceAssoc(length, type); 193 break; 194 195 /* 196 * Various Class Specific 197 */ 198 case UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_INTERFACE: 199 if (mCurInterfaceDescriptor != null) { 200 switch (mCurInterfaceDescriptor.getUsbClass()) { 201 case UsbDescriptor.CLASSID_AUDIO: 202 descriptor = 203 UsbACInterface.allocDescriptor(this, stream, length, type); 204 if (descriptor instanceof UsbMSMidiHeader) { 205 mCurInterfaceDescriptor.setMidiHeaderInterfaceDescriptor( 206 descriptor); 207 } 208 break; 209 210 case UsbDescriptor.CLASSID_VIDEO: 211 if (DEBUG) { 212 Log.d(TAG, " UsbDescriptor.CLASSID_VIDEO"); 213 } 214 descriptor = 215 UsbVCInterface.allocDescriptor(this, stream, length, type); 216 break; 217 218 case UsbDescriptor.CLASSID_AUDIOVIDEO: 219 if (DEBUG) { 220 Log.d(TAG, " UsbDescriptor.CLASSID_AUDIOVIDEO"); 221 } 222 break; 223 224 default: 225 Log.w(TAG, " Unparsed Class-specific"); 226 break; 227 } 228 } 229 break; 230 231 case UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_ENDPOINT: 232 if (mCurInterfaceDescriptor != null) { 233 int subClass = mCurInterfaceDescriptor.getUsbClass(); 234 switch (subClass) { 235 case UsbDescriptor.CLASSID_AUDIO: { 236 Byte subType = stream.getByte(); 237 if (DEBUG) { 238 Log.d(TAG, "UsbDescriptor.CLASSID_AUDIO type:0x" 239 + Integer.toHexString(type)); 240 } 241 descriptor = UsbACEndpoint.allocDescriptor(this, length, type, 242 subType); 243 } 244 break; 245 246 case UsbDescriptor.CLASSID_VIDEO: { 247 Byte subType = stream.getByte(); 248 if (DEBUG) { 249 Log.d(TAG, "UsbDescriptor.CLASSID_VIDEO type:0x" 250 + Integer.toHexString(type)); 251 } 252 descriptor = UsbVCEndpoint.allocDescriptor(this, length, type, 253 subType); 254 } 255 break; 256 257 case UsbDescriptor.CLASSID_AUDIOVIDEO: 258 if (DEBUG) { 259 Log.d(TAG, "UsbDescriptor.CLASSID_AUDIOVIDEO type:0x" 260 + Integer.toHexString(type)); 261 } 262 break; 263 264 default: 265 Log.w(TAG, " Unparsed Class-specific Endpoint:0x" 266 + Integer.toHexString(subClass)); 267 break; 268 } 269 if (mCurEndpointDescriptor != null && descriptor != null) { 270 mCurEndpointDescriptor.setClassSpecificEndpointDescriptor(descriptor); 271 } 272 } 273 break; 274 275 default: 276 break; 277 } 278 279 if (descriptor == null) { 280 // Unknown Descriptor 281 descriptor = new UsbUnknown(length, type); 282 } 283 284 return descriptor; 285 } 286 getDeviceDescriptor()287 public UsbDeviceDescriptor getDeviceDescriptor() { 288 return mDeviceDescriptor; 289 } 290 getCurInterface()291 public UsbInterfaceDescriptor getCurInterface() { 292 return mCurInterfaceDescriptor; 293 } 294 295 /** 296 * @hide 297 */ parseDescriptors(byte[] descriptors)298 public void parseDescriptors(byte[] descriptors) { 299 ByteStream stream = new ByteStream(descriptors); 300 while (stream.available() > 0) { 301 UsbDescriptor descriptor = null; 302 try { 303 descriptor = allocDescriptor(stream); 304 } catch (Exception ex) { 305 Log.e(TAG, "Exception allocating USB descriptor.", ex); 306 } 307 308 if (descriptor != null) { 309 // Parse 310 try { 311 descriptor.parseRawDescriptors(stream); 312 313 // Clean up 314 descriptor.postParse(stream); 315 } catch (Exception ex) { 316 // Clean up, compute error status 317 descriptor.postParse(stream); 318 319 // Report 320 Log.w(TAG, "Exception parsing USB descriptors. type:0x" + descriptor.getType() 321 + " status:" + descriptor.getStatus()); 322 if (DEBUG) { 323 // Show full stack trace if debugging 324 Log.e(TAG, "Exception parsing USB descriptors.", ex); 325 } 326 StackTraceElement[] stackElems = ex.getStackTrace(); 327 if (stackElems.length > 0) { 328 Log.i(TAG, " class:" + stackElems[0].getClassName() 329 + " @ " + stackElems[0].getLineNumber()); 330 } 331 if (stackElems.length > 1) { 332 Log.i(TAG, " class:" + stackElems[1].getClassName() 333 + " @ " + stackElems[1].getLineNumber()); 334 } 335 336 // Finish up 337 descriptor.setStatus(UsbDescriptor.STATUS_PARSE_EXCEPTION); 338 } finally { 339 mDescriptors.add(descriptor); 340 } 341 } 342 } 343 if (DEBUG) { 344 Log.d(TAG, "parseDescriptors() - end " + mDescriptors.size() + " descriptors."); 345 } 346 } 347 getRawDescriptors()348 public byte[] getRawDescriptors() { 349 return getRawDescriptors_native(mDeviceAddr); 350 } 351 getRawDescriptors_native(String deviceAddr)352 private native byte[] getRawDescriptors_native(String deviceAddr); 353 354 /** 355 * @hide 356 */ getDescriptorString(int stringId)357 public String getDescriptorString(int stringId) { 358 return getDescriptorString_native(mDeviceAddr, stringId); 359 } 360 getDescriptorString_native(String deviceAddr, int stringId)361 private native String getDescriptorString_native(String deviceAddr, int stringId); 362 getParsingSpec()363 public int getParsingSpec() { 364 return mDeviceDescriptor != null ? mDeviceDescriptor.getSpec() : 0; 365 } 366 getDescriptors()367 public ArrayList<UsbDescriptor> getDescriptors() { 368 return mDescriptors; 369 } 370 371 /** 372 * @hide 373 */ toAndroidUsbDeviceBuilder()374 public UsbDevice.Builder toAndroidUsbDeviceBuilder() { 375 if (mDeviceDescriptor == null) { 376 Log.e(TAG, "toAndroidUsbDevice() ERROR - No Device Descriptor"); 377 return null; 378 } 379 380 UsbDevice.Builder builder = mDeviceDescriptor.toAndroid(this); 381 if (builder == null) { 382 Log.e(TAG, "toAndroidUsbDevice() ERROR Creating Device"); 383 } 384 return builder; 385 } 386 387 /** 388 * @hide 389 */ getDescriptors(byte type)390 public ArrayList<UsbDescriptor> getDescriptors(byte type) { 391 ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>(); 392 for (UsbDescriptor descriptor : mDescriptors) { 393 if (descriptor.getType() == type) { 394 list.add(descriptor); 395 } 396 } 397 return list; 398 } 399 400 /** 401 * @hide 402 */ getInterfaceDescriptorsForClass(int usbClass)403 public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(int usbClass) { 404 ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>(); 405 for (UsbDescriptor descriptor : mDescriptors) { 406 // ensure that this isn't an unrecognized DESCRIPTORTYPE_INTERFACE 407 if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_INTERFACE) { 408 if (descriptor instanceof UsbInterfaceDescriptor) { 409 UsbInterfaceDescriptor intrDesc = (UsbInterfaceDescriptor) descriptor; 410 if (intrDesc.getUsbClass() == usbClass) { 411 list.add(descriptor); 412 } 413 } else { 414 Log.w(TAG, "Unrecognized Interface l: " + descriptor.getLength() 415 + " t:0x" + Integer.toHexString(descriptor.getType())); 416 } 417 } 418 } 419 return list; 420 } 421 422 /** 423 * @hide 424 */ getACInterfaceDescriptors(byte subtype, int subclass)425 public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, int subclass) { 426 ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>(); 427 for (UsbDescriptor descriptor : mDescriptors) { 428 if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_INTERFACE) { 429 // ensure that this isn't an unrecognized DESCRIPTORTYPE_CLASSSPECIFIC_INTERFACE 430 if (descriptor instanceof UsbACInterface) { 431 UsbACInterface acDescriptor = (UsbACInterface) descriptor; 432 if (acDescriptor.getSubtype() == subtype 433 && acDescriptor.getSubclass() == subclass) { 434 list.add(descriptor); 435 } 436 } else { 437 Log.w(TAG, "Unrecognized Audio Interface len: " + descriptor.getLength() 438 + " type:0x" + Integer.toHexString(descriptor.getType())); 439 } 440 } 441 } 442 return list; 443 } 444 445 /* 446 * Attribute predicates 447 */ 448 /** 449 * @hide 450 */ hasInput()451 public boolean hasInput() { 452 if (DEBUG) { 453 Log.d(TAG, "---- hasInput()"); 454 } 455 ArrayList<UsbDescriptor> acDescriptors = 456 getACInterfaceDescriptors(UsbACInterface.ACI_INPUT_TERMINAL, 457 UsbACInterface.AUDIO_AUDIOCONTROL); 458 boolean hasInput = false; 459 for (UsbDescriptor descriptor : acDescriptors) { 460 if (descriptor instanceof UsbACTerminal) { 461 UsbACTerminal inDescr = (UsbACTerminal) descriptor; 462 // Check for input and bi-directional terminal types 463 int type = inDescr.getTerminalType(); 464 if (DEBUG) { 465 Log.d(TAG, " type:0x" + Integer.toHexString(type)); 466 } 467 int terminalCategory = type & ~0xFF; 468 if (terminalCategory != UsbTerminalTypes.TERMINAL_USB_UNDEFINED 469 && terminalCategory != UsbTerminalTypes.TERMINAL_OUT_UNDEFINED) { 470 // If not explicitly a USB connection or output, it could be an input. 471 hasInput = true; 472 break; 473 } 474 } else { 475 Log.w(TAG, "Undefined Audio Input terminal l: " + descriptor.getLength() 476 + " t:0x" + Integer.toHexString(descriptor.getType())); 477 } 478 } 479 480 if (DEBUG) { 481 Log.d(TAG, "hasInput() = " + hasInput); 482 } 483 return hasInput; 484 } 485 486 /** 487 * @hide 488 */ hasOutput()489 public boolean hasOutput() { 490 if (DEBUG) { 491 Log.d(TAG, "---- hasOutput()"); 492 } 493 ArrayList<UsbDescriptor> acDescriptors = 494 getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL, 495 UsbACInterface.AUDIO_AUDIOCONTROL); 496 boolean hasOutput = false; 497 for (UsbDescriptor descriptor : acDescriptors) { 498 if (descriptor instanceof UsbACTerminal) { 499 UsbACTerminal outDescr = (UsbACTerminal) descriptor; 500 // Check for output and bi-directional terminal types 501 int type = outDescr.getTerminalType(); 502 if (DEBUG) { 503 Log.d(TAG, " type:0x" + Integer.toHexString(type)); 504 } 505 int terminalCategory = type & ~0xFF; 506 if (terminalCategory != UsbTerminalTypes.TERMINAL_USB_UNDEFINED 507 && terminalCategory != UsbTerminalTypes.TERMINAL_IN_UNDEFINED) { 508 // If not explicitly a USB connection or input, it could be an output. 509 hasOutput = true; 510 break; 511 } 512 } else { 513 Log.w(TAG, "Undefined Audio Input terminal l: " + descriptor.getLength() 514 + " t:0x" + Integer.toHexString(descriptor.getType())); 515 } 516 } 517 if (DEBUG) { 518 Log.d(TAG, "hasOutput() = " + hasOutput); 519 } 520 return hasOutput; 521 } 522 523 /** 524 * @hide 525 */ hasMic()526 public boolean hasMic() { 527 ArrayList<UsbDescriptor> acDescriptors = 528 getACInterfaceDescriptors(UsbACInterface.ACI_INPUT_TERMINAL, 529 UsbACInterface.AUDIO_AUDIOCONTROL); 530 for (UsbDescriptor descriptor : acDescriptors) { 531 if (descriptor instanceof UsbACTerminal) { 532 UsbACTerminal inDescr = (UsbACTerminal) descriptor; 533 if (inDescr.isInputTerminal()) { 534 return true; 535 } 536 } else { 537 Log.w(TAG, "Undefined Audio Input terminal l: " + descriptor.getLength() 538 + " t:0x" + Integer.toHexString(descriptor.getType())); 539 } 540 } 541 return false; 542 } 543 544 /** 545 * @hide 546 */ hasSpeaker()547 public boolean hasSpeaker() { 548 boolean hasSpeaker = false; 549 550 ArrayList<UsbDescriptor> acDescriptors = 551 getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL, 552 UsbACInterface.AUDIO_AUDIOCONTROL); 553 for (UsbDescriptor descriptor : acDescriptors) { 554 if (descriptor instanceof UsbACTerminal) { 555 UsbACTerminal outDescr = (UsbACTerminal) descriptor; 556 if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER 557 || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES 558 || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) { 559 hasSpeaker = true; 560 break; 561 } 562 } else { 563 Log.w(TAG, "Undefined Audio Output terminal l: " + descriptor.getLength() 564 + " t:0x" + Integer.toHexString(descriptor.getType())); 565 } 566 } 567 568 return hasSpeaker; 569 } 570 571 /** 572 *@ hide 573 */ hasAudioInterface()574 public boolean hasAudioInterface() { 575 ArrayList<UsbDescriptor> descriptors = 576 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO); 577 return !descriptors.isEmpty(); 578 } 579 580 /** 581 * Returns true only if there is a terminal whose subtype and terminal type are the same as 582 * the given values. 583 * @hide 584 */ hasAudioTerminal(int subType, int terminalType)585 public boolean hasAudioTerminal(int subType, int terminalType) { 586 for (UsbDescriptor descriptor : mDescriptors) { 587 if (descriptor instanceof UsbACTerminal) { 588 if (((UsbACTerminal) descriptor).getSubclass() == UsbDescriptor.AUDIO_AUDIOCONTROL 589 && ((UsbACTerminal) descriptor).getSubtype() == subType 590 && ((UsbACTerminal) descriptor).getTerminalType() == terminalType) { 591 return true; 592 } 593 } 594 } 595 return false; 596 } 597 598 /** 599 * Returns true only if there is an interface whose subtype is the same as the given one and 600 * terminal type is different from the given one. 601 * @hide 602 */ hasAudioTerminalExcludeType(int subType, int excludedTerminalType)603 public boolean hasAudioTerminalExcludeType(int subType, int excludedTerminalType) { 604 for (UsbDescriptor descriptor : mDescriptors) { 605 if (descriptor instanceof UsbACTerminal) { 606 if (((UsbACTerminal) descriptor).getSubclass() == UsbDescriptor.AUDIO_AUDIOCONTROL 607 && ((UsbACTerminal) descriptor).getSubtype() == subType 608 && ((UsbACTerminal) descriptor).getTerminalType() != excludedTerminalType) { 609 return true; 610 } 611 } 612 } 613 return false; 614 } 615 616 /** 617 * @hide 618 */ hasAudioPlayback()619 public boolean hasAudioPlayback() { 620 return hasAudioTerminalExcludeType( 621 UsbACInterface.ACI_OUTPUT_TERMINAL, UsbTerminalTypes.TERMINAL_USB_STREAMING) 622 && hasAudioTerminal( 623 UsbACInterface.ACI_INPUT_TERMINAL, UsbTerminalTypes.TERMINAL_USB_STREAMING); 624 } 625 626 /** 627 * @hide 628 */ hasAudioCapture()629 public boolean hasAudioCapture() { 630 return hasAudioTerminalExcludeType( 631 UsbACInterface.ACI_INPUT_TERMINAL, UsbTerminalTypes.TERMINAL_USB_STREAMING) 632 && hasAudioTerminal( 633 UsbACInterface.ACI_OUTPUT_TERMINAL, 634 UsbTerminalTypes.TERMINAL_USB_STREAMING); 635 } 636 637 /** 638 * @hide 639 */ hasVideoCapture()640 public boolean hasVideoCapture() { 641 for (UsbDescriptor descriptor : mDescriptors) { 642 if (descriptor instanceof UsbVCInputTerminal) { 643 return true; 644 } 645 } 646 return false; 647 } 648 649 /** 650 * @hide 651 */ hasVideoPlayback()652 public boolean hasVideoPlayback() { 653 for (UsbDescriptor descriptor : mDescriptors) { 654 if (descriptor instanceof UsbVCOutputTerminal) { 655 return true; 656 } 657 } 658 return false; 659 } 660 661 /** 662 * @hide 663 */ hasHIDInterface()664 public boolean hasHIDInterface() { 665 ArrayList<UsbDescriptor> descriptors = 666 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_HID); 667 return !descriptors.isEmpty(); 668 } 669 670 /** 671 * @hide 672 */ hasStorageInterface()673 public boolean hasStorageInterface() { 674 ArrayList<UsbDescriptor> descriptors = 675 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_STORAGE); 676 return !descriptors.isEmpty(); 677 } 678 679 /** 680 * @hide 681 */ hasMIDIInterface()682 public boolean hasMIDIInterface() { 683 ArrayList<UsbDescriptor> descriptors = 684 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO); 685 for (UsbDescriptor descriptor : descriptors) { 686 // enusure that this isn't an unrecognized interface descriptor 687 if (descriptor instanceof UsbInterfaceDescriptor) { 688 UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor; 689 if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) { 690 return true; 691 } 692 } else { 693 Log.w(TAG, "Undefined Audio Class Interface l: " + descriptor.getLength() 694 + " t:0x" + Integer.toHexString(descriptor.getType())); 695 } 696 } 697 return false; 698 } 699 700 /** 701 * @hide 702 */ containsUniversalMidiDeviceEndpoint()703 public boolean containsUniversalMidiDeviceEndpoint() { 704 ArrayList<UsbInterfaceDescriptor> interfaceDescriptors = 705 findUniversalMidiInterfaceDescriptors(); 706 return doesInterfaceContainEndpoint(interfaceDescriptors); 707 } 708 709 /** 710 * @hide 711 */ containsLegacyMidiDeviceEndpoint()712 public boolean containsLegacyMidiDeviceEndpoint() { 713 ArrayList<UsbInterfaceDescriptor> interfaceDescriptors = 714 findLegacyMidiInterfaceDescriptors(); 715 return doesInterfaceContainEndpoint(interfaceDescriptors); 716 } 717 718 /** 719 * @hide 720 */ doesInterfaceContainEndpoint( ArrayList<UsbInterfaceDescriptor> interfaceDescriptors)721 public boolean doesInterfaceContainEndpoint( 722 ArrayList<UsbInterfaceDescriptor> interfaceDescriptors) { 723 int outputCount = 0; 724 int inputCount = 0; 725 for (int interfaceIndex = 0; interfaceIndex < interfaceDescriptors.size(); 726 interfaceIndex++) { 727 UsbInterfaceDescriptor interfaceDescriptor = interfaceDescriptors.get(interfaceIndex); 728 for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints(); 729 endpointIndex++) { 730 UsbEndpointDescriptor endpoint = 731 interfaceDescriptor.getEndpointDescriptor(endpointIndex); 732 // 0 is output, 1 << 7 is input. 733 if (endpoint.getDirection() == 0) { 734 outputCount++; 735 } else { 736 inputCount++; 737 } 738 } 739 } 740 return (outputCount > 0) || (inputCount > 0); 741 } 742 743 /** 744 * @hide 745 */ findUniversalMidiInterfaceDescriptors()746 public ArrayList<UsbInterfaceDescriptor> findUniversalMidiInterfaceDescriptors() { 747 return findMidiInterfaceDescriptors(MS_MIDI_2_0); 748 } 749 750 /** 751 * @hide 752 */ findLegacyMidiInterfaceDescriptors()753 public ArrayList<UsbInterfaceDescriptor> findLegacyMidiInterfaceDescriptors() { 754 return findMidiInterfaceDescriptors(MS_MIDI_1_0); 755 } 756 757 /** 758 * @hide 759 */ findMidiInterfaceDescriptors(int type)760 private ArrayList<UsbInterfaceDescriptor> findMidiInterfaceDescriptors(int type) { 761 int count = 0; 762 ArrayList<UsbDescriptor> descriptors = 763 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO); 764 ArrayList<UsbInterfaceDescriptor> midiInterfaces = 765 new ArrayList<UsbInterfaceDescriptor>(); 766 767 for (UsbDescriptor descriptor : descriptors) { 768 // ensure that this isn't an unrecognized interface descriptor 769 if (descriptor instanceof UsbInterfaceDescriptor) { 770 UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor; 771 if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) { 772 UsbDescriptor midiHeaderDescriptor = 773 interfaceDescriptor.getMidiHeaderInterfaceDescriptor(); 774 if (midiHeaderDescriptor != null) { 775 if (midiHeaderDescriptor instanceof UsbMSMidiHeader) { 776 UsbMSMidiHeader midiHeader = 777 (UsbMSMidiHeader) midiHeaderDescriptor; 778 if (midiHeader.getMidiStreamingClass() == type) { 779 midiInterfaces.add(interfaceDescriptor); 780 } 781 } 782 } 783 } 784 } else { 785 Log.w(TAG, "Undefined Audio Class Interface l: " + descriptor.getLength() 786 + " t:0x" + Integer.toHexString(descriptor.getType())); 787 } 788 } 789 return midiInterfaces; 790 } 791 792 /** 793 * @hide 794 */ calculateMidiInterfaceDescriptorsCount()795 public int calculateMidiInterfaceDescriptorsCount() { 796 int count = 0; 797 ArrayList<UsbDescriptor> descriptors = 798 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO); 799 for (UsbDescriptor descriptor : descriptors) { 800 // ensure that this isn't an unrecognized interface descriptor 801 if (descriptor instanceof UsbInterfaceDescriptor) { 802 UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor; 803 if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) { 804 UsbDescriptor midiHeaderDescriptor = 805 interfaceDescriptor.getMidiHeaderInterfaceDescriptor(); 806 if (midiHeaderDescriptor != null) { 807 if (midiHeaderDescriptor instanceof UsbMSMidiHeader) { 808 UsbMSMidiHeader midiHeader = 809 (UsbMSMidiHeader) midiHeaderDescriptor; 810 count++; 811 } 812 } 813 } 814 } else { 815 Log.w(TAG, "Undefined Audio Class Interface l: " + descriptor.getLength() 816 + " t:0x" + Integer.toHexString(descriptor.getType())); 817 } 818 } 819 return count; 820 } 821 822 /** 823 * @hide 824 */ calculateNumLegacyMidiPorts(boolean isOutput)825 private int calculateNumLegacyMidiPorts(boolean isOutput) { 826 // Only look at the first config. 827 UsbConfigDescriptor configDescriptor = null; 828 for (UsbDescriptor descriptor : mDescriptors) { 829 if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_CONFIG) { 830 if (descriptor instanceof UsbConfigDescriptor) { 831 configDescriptor = (UsbConfigDescriptor) descriptor; 832 break; 833 } else { 834 Log.w(TAG, "Unrecognized Config l: " + descriptor.getLength() 835 + " t:0x" + Integer.toHexString(descriptor.getType())); 836 } 837 } 838 } 839 if (configDescriptor == null) { 840 Log.w(TAG, "Config not found"); 841 return 0; 842 } 843 844 ArrayList<UsbInterfaceDescriptor> legacyMidiInterfaceDescriptors = 845 new ArrayList<UsbInterfaceDescriptor>(); 846 for (UsbInterfaceDescriptor interfaceDescriptor 847 : configDescriptor.getInterfaceDescriptors()) { 848 if (interfaceDescriptor.getUsbClass() == UsbDescriptor.CLASSID_AUDIO) { 849 if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) { 850 UsbDescriptor midiHeaderDescriptor = 851 interfaceDescriptor.getMidiHeaderInterfaceDescriptor(); 852 if (midiHeaderDescriptor != null) { 853 if (midiHeaderDescriptor instanceof UsbMSMidiHeader) { 854 UsbMSMidiHeader midiHeader = 855 (UsbMSMidiHeader) midiHeaderDescriptor; 856 if (midiHeader.getMidiStreamingClass() == MS_MIDI_1_0) { 857 legacyMidiInterfaceDescriptors.add(interfaceDescriptor); 858 } 859 } 860 } 861 } 862 } 863 } 864 865 int count = 0; 866 for (UsbInterfaceDescriptor interfaceDescriptor : legacyMidiInterfaceDescriptors) { 867 for (int i = 0; i < interfaceDescriptor.getNumEndpoints(); i++) { 868 UsbEndpointDescriptor endpoint = 869 interfaceDescriptor.getEndpointDescriptor(i); 870 // 0 is output, 1 << 7 is input. 871 if ((endpoint.getDirection() == 0) == isOutput) { 872 UsbDescriptor classSpecificEndpointDescriptor = 873 endpoint.getClassSpecificEndpointDescriptor(); 874 if (classSpecificEndpointDescriptor != null 875 && (classSpecificEndpointDescriptor instanceof UsbACMidi10Endpoint)) { 876 UsbACMidi10Endpoint midiEndpoint = 877 (UsbACMidi10Endpoint) classSpecificEndpointDescriptor; 878 count += midiEndpoint.getNumJacks(); 879 } 880 } 881 } 882 } 883 return count; 884 } 885 886 /** 887 * @hide 888 */ calculateNumLegacyMidiInputs()889 public int calculateNumLegacyMidiInputs() { 890 return calculateNumLegacyMidiPorts(false /*isOutput*/); 891 } 892 893 /** 894 * @hide 895 */ calculateNumLegacyMidiOutputs()896 public int calculateNumLegacyMidiOutputs() { 897 return calculateNumLegacyMidiPorts(true /*isOutput*/); 898 } 899 900 /** 901 * @hide 902 */ getInputHeadsetProbability()903 public float getInputHeadsetProbability() { 904 if (hasMIDIInterface()) { 905 return 0.0f; 906 } 907 908 float probability = 0.0f; 909 910 // Look for a "speaker" 911 boolean hasSpeaker = hasSpeaker(); 912 913 if (hasMic()) { 914 if (hasSpeaker) { 915 probability += 0.75f; 916 } 917 if (hasHIDInterface()) { 918 probability += 0.25f; 919 } 920 if (getMaximumInputChannelCount() > 1) { 921 // A headset is more likely to only support mono capture. 922 probability -= 0.25f; 923 } 924 } 925 926 return probability; 927 } 928 929 /** 930 * getInputHeadsetProbability() reports a probability of a USB Input peripheral being a 931 * headset. The probability range is between 0.0f (definitely NOT a headset) and 932 * 1.0f (definitely IS a headset). A probability of 0.75f seems sufficient 933 * to count on the peripheral being a headset. 934 * To align with the output device type, only treat the device as input headset if it is 935 * an output headset. 936 */ isInputHeadset()937 public boolean isInputHeadset() { 938 return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER && isOutputHeadset(); 939 } 940 941 // TODO: Up/Downmix process descriptor is not yet parsed, which may affect the result here. getMaximumChannelCount()942 private int getMaximumChannelCount() { 943 int maxChannelCount = 0; 944 for (UsbDescriptor descriptor : mDescriptors) { 945 if (descriptor instanceof UsbAudioChannelCluster) { 946 maxChannelCount = Math.max(maxChannelCount, 947 ((UsbAudioChannelCluster) descriptor).getChannelCount()); 948 } 949 } 950 return maxChannelCount; 951 } 952 getMaximumInputChannelCount()953 private int getMaximumInputChannelCount() { 954 int maxChannelCount = 0; 955 ArrayList<UsbDescriptor> acDescriptors = 956 getACInterfaceDescriptors(UsbACInterface.ACI_INPUT_TERMINAL, 957 UsbACInterface.AUDIO_AUDIOCONTROL); 958 for (UsbDescriptor descriptor : acDescriptors) { 959 if (!(descriptor instanceof UsbACTerminal)) { 960 continue; 961 } 962 UsbACTerminal inDescr = (UsbACTerminal) descriptor; 963 if (!inDescr.isInputTerminal()) { 964 continue; 965 } 966 // For an input terminal, it should at lease has 1 channel. 967 // Comparing the max channel count with 1 here in case the USB device doesn't report 968 // audio channel cluster. 969 maxChannelCount = Math.max(maxChannelCount, 1); 970 if (!(descriptor instanceof UsbAudioChannelCluster)) { 971 continue; 972 } 973 maxChannelCount = Math.max(maxChannelCount, 974 ((UsbAudioChannelCluster) descriptor).getChannelCount()); 975 } 976 return maxChannelCount; 977 } 978 979 /** 980 * @hide 981 */ getOutputHeadsetLikelihood()982 public float getOutputHeadsetLikelihood() { 983 if (hasMIDIInterface()) { 984 return 0.0f; 985 } 986 987 float likelihood = 0.0f; 988 ArrayList<UsbDescriptor> acDescriptors; 989 990 // Look for a "speaker" 991 boolean hasSpeaker = false; 992 boolean hasAssociatedInputTerminal = false; 993 boolean hasHeadphoneOrHeadset = false; 994 acDescriptors = 995 getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL, 996 UsbACInterface.AUDIO_AUDIOCONTROL); 997 for (UsbDescriptor descriptor : acDescriptors) { 998 if (descriptor instanceof UsbACTerminal) { 999 UsbACTerminal outDescr = (UsbACTerminal) descriptor; 1000 if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER) { 1001 hasSpeaker = true; 1002 if (outDescr.getAssocTerminal() != 0x0) { 1003 hasAssociatedInputTerminal = true; 1004 } 1005 } else if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES 1006 || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) { 1007 hasHeadphoneOrHeadset = true; 1008 } 1009 } else { 1010 Log.w(TAG, "Undefined Audio Output terminal l: " + descriptor.getLength() 1011 + " t:0x" + Integer.toHexString(descriptor.getType())); 1012 } 1013 } 1014 1015 if (hasHeadphoneOrHeadset) { 1016 likelihood += 0.75f; 1017 } else if (hasSpeaker) { 1018 // The device only reports output terminal as speaker. Try to figure out if the device 1019 // is a headset or not by checking if it has associated input terminal and if multiple 1020 // channels are supported or not. 1021 likelihood += 0.5f; 1022 if (hasAssociatedInputTerminal) { 1023 likelihood += 0.25f; 1024 } 1025 if (getMaximumChannelCount() > 2) { 1026 // When multiple channels are supported, it is less likely to be a headset. 1027 likelihood -= 0.25f; 1028 } 1029 } 1030 1031 if ((hasHeadphoneOrHeadset || hasSpeaker) && hasHIDInterface()) { 1032 likelihood += 0.25f; 1033 } 1034 1035 return likelihood; 1036 } 1037 1038 /** 1039 * getOutputHeadsetProbability() reports a probability of a USB Output peripheral being a 1040 * headset. The probability range is between 0.0f (definitely NOT a headset) and 1041 * 1.0f (definitely IS a headset). A probability of 0.75f seems sufficient 1042 * to count on the peripheral being a headset. 1043 */ isOutputHeadset()1044 public boolean isOutputHeadset() { 1045 return getOutputHeadsetLikelihood() >= OUT_HEADSET_TRIGGER; 1046 } 1047 1048 /** 1049 * isDock() indicates if the connected USB output peripheral is a docking station with 1050 * audio output. 1051 * A valid audio dock must declare only one audio output control terminal of type 1052 * TERMINAL_EXTERN_DIGITAL. 1053 */ isDock()1054 public boolean isDock() { 1055 if (hasMIDIInterface() || hasHIDInterface()) { 1056 return false; 1057 } 1058 1059 ArrayList<UsbDescriptor> acDescriptors = 1060 getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL, 1061 UsbACInterface.AUDIO_AUDIOCONTROL); 1062 1063 if (acDescriptors.size() != 1) { 1064 return false; 1065 } 1066 1067 if (acDescriptors.get(0) instanceof UsbACTerminal) { 1068 UsbACTerminal outDescr = (UsbACTerminal) acDescriptors.get(0); 1069 if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_EXTERN_DIGITAL) { 1070 return true; 1071 } 1072 } else { 1073 Log.w(TAG, "Undefined Audio Output terminal l: " + acDescriptors.get(0).getLength() 1074 + " t:0x" + Integer.toHexString(acDescriptors.get(0).getType())); 1075 } 1076 return false; 1077 } 1078 1079 } 1080