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 private static final boolean DEBUG = false; 30 31 private final String mDeviceAddr; 32 33 // Descriptor Objects 34 private static final int DESCRIPTORS_ALLOC_SIZE = 128; 35 private final ArrayList<UsbDescriptor> mDescriptors; 36 37 private UsbDeviceDescriptor mDeviceDescriptor; 38 private UsbConfigDescriptor mCurConfigDescriptor; 39 private UsbInterfaceDescriptor mCurInterfaceDescriptor; 40 41 // The AudioClass spec implemented by the AudioClass Interfaces 42 // This may well be different than the overall USB Spec. 43 // Obtained from the first AudioClass Header descriptor. 44 private int mACInterfacesSpec = UsbDeviceDescriptor.USBSPEC_1_0; 45 46 /** 47 * Connect this parser to an existing set of already parsed descriptors. 48 * This is useful for reporting. 49 */ UsbDescriptorParser(String deviceAddr, ArrayList<UsbDescriptor> descriptors)50 public UsbDescriptorParser(String deviceAddr, ArrayList<UsbDescriptor> descriptors) { 51 mDeviceAddr = deviceAddr; 52 mDescriptors = descriptors; 53 //TODO some error checking here.... 54 mDeviceDescriptor = (UsbDeviceDescriptor) descriptors.get(0); 55 } 56 57 /** 58 * Connect this parser to an byte array containing unparsed (raw) device descriptors 59 * to be parsed (and parse them). Useful for parsing a stored descriptor buffer. 60 */ UsbDescriptorParser(String deviceAddr, byte[] rawDescriptors)61 public UsbDescriptorParser(String deviceAddr, byte[] rawDescriptors) { 62 mDeviceAddr = deviceAddr; 63 mDescriptors = new ArrayList<UsbDescriptor>(DESCRIPTORS_ALLOC_SIZE); 64 parseDescriptors(rawDescriptors); 65 } 66 getDeviceAddr()67 public String getDeviceAddr() { 68 return mDeviceAddr; 69 } 70 71 /** 72 * @return the USB Spec value associated with the Device descriptor for the 73 * descriptors stream being parsed. 74 * 75 * @throws IllegalArgumentException 76 */ getUsbSpec()77 public int getUsbSpec() { 78 if (mDeviceDescriptor != null) { 79 return mDeviceDescriptor.getSpec(); 80 } else { 81 throw new IllegalArgumentException(); 82 } 83 } 84 setACInterfaceSpec(int spec)85 public void setACInterfaceSpec(int spec) { 86 mACInterfacesSpec = spec; 87 } 88 getACInterfaceSpec()89 public int getACInterfaceSpec() { 90 return mACInterfacesSpec; 91 } 92 93 private class UsbDescriptorsStreamFormatException extends Exception { 94 String mMessage; UsbDescriptorsStreamFormatException(String message)95 UsbDescriptorsStreamFormatException(String message) { 96 mMessage = message; 97 } 98 toString()99 public String toString() { 100 return "Descriptor Stream Format Exception: " + mMessage; 101 } 102 } 103 104 /** 105 * The probability (as returned by getHeadsetProbability() at which we conclude 106 * the peripheral is a headset. 107 */ 108 private static final float IN_HEADSET_TRIGGER = 0.75f; 109 private static final float OUT_HEADSET_TRIGGER = 0.75f; 110 allocDescriptor(ByteStream stream)111 private UsbDescriptor allocDescriptor(ByteStream stream) 112 throws UsbDescriptorsStreamFormatException { 113 stream.resetReadCount(); 114 115 int length = stream.getUnsignedByte(); 116 byte type = stream.getByte(); 117 118 UsbDescriptor descriptor = null; 119 switch (type) { 120 /* 121 * Standard 122 */ 123 case UsbDescriptor.DESCRIPTORTYPE_DEVICE: 124 descriptor = mDeviceDescriptor = new UsbDeviceDescriptor(length, type); 125 break; 126 127 case UsbDescriptor.DESCRIPTORTYPE_CONFIG: 128 descriptor = mCurConfigDescriptor = new UsbConfigDescriptor(length, type); 129 if (mDeviceDescriptor != null) { 130 mDeviceDescriptor.addConfigDescriptor(mCurConfigDescriptor); 131 } else { 132 Log.e(TAG, "Config Descriptor found with no associated Device Descriptor!"); 133 throw new UsbDescriptorsStreamFormatException( 134 "Config Descriptor found with no associated Device Descriptor!"); 135 } 136 break; 137 138 case UsbDescriptor.DESCRIPTORTYPE_INTERFACE: 139 descriptor = mCurInterfaceDescriptor = new UsbInterfaceDescriptor(length, type); 140 if (mCurConfigDescriptor != null) { 141 mCurConfigDescriptor.addInterfaceDescriptor(mCurInterfaceDescriptor); 142 } else { 143 Log.e(TAG, "Interface Descriptor found with no associated Config Descriptor!"); 144 throw new UsbDescriptorsStreamFormatException( 145 "Interface Descriptor found with no associated Config Descriptor!"); 146 } 147 break; 148 149 case UsbDescriptor.DESCRIPTORTYPE_ENDPOINT: 150 descriptor = new UsbEndpointDescriptor(length, type); 151 if (mCurInterfaceDescriptor != null) { 152 mCurInterfaceDescriptor.addEndpointDescriptor( 153 (UsbEndpointDescriptor) descriptor); 154 } else { 155 Log.e(TAG, 156 "Endpoint Descriptor found with no associated Interface Descriptor!"); 157 throw new UsbDescriptorsStreamFormatException( 158 "Endpoint Descriptor found with no associated Interface Descriptor!"); 159 } 160 break; 161 162 /* 163 * HID 164 */ 165 case UsbDescriptor.DESCRIPTORTYPE_HID: 166 descriptor = new UsbHIDDescriptor(length, type); 167 break; 168 169 /* 170 * Other 171 */ 172 case UsbDescriptor.DESCRIPTORTYPE_INTERFACEASSOC: 173 descriptor = new UsbInterfaceAssoc(length, type); 174 break; 175 176 /* 177 * Audio Class Specific 178 */ 179 case UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE: 180 descriptor = UsbACInterface.allocDescriptor(this, stream, length, type); 181 break; 182 183 case UsbDescriptor.DESCRIPTORTYPE_AUDIO_ENDPOINT: 184 descriptor = UsbACEndpoint.allocDescriptor(this, length, type); 185 break; 186 187 default: 188 break; 189 } 190 191 if (descriptor == null) { 192 // Unknown Descriptor 193 Log.i(TAG, "Unknown Descriptor len: " + length + " type:0x" 194 + Integer.toHexString(type)); 195 descriptor = new UsbUnknown(length, type); 196 } 197 198 return descriptor; 199 } 200 getDeviceDescriptor()201 public UsbDeviceDescriptor getDeviceDescriptor() { 202 return mDeviceDescriptor; 203 } 204 getCurInterface()205 public UsbInterfaceDescriptor getCurInterface() { 206 return mCurInterfaceDescriptor; 207 } 208 209 /** 210 * @hide 211 */ parseDescriptors(byte[] descriptors)212 public void parseDescriptors(byte[] descriptors) { 213 if (DEBUG) { 214 Log.d(TAG, "parseDescriptors() - start"); 215 } 216 217 ByteStream stream = new ByteStream(descriptors); 218 while (stream.available() > 0) { 219 UsbDescriptor descriptor = null; 220 try { 221 descriptor = allocDescriptor(stream); 222 } catch (Exception ex) { 223 Log.e(TAG, "Exception allocating USB descriptor.", ex); 224 } 225 226 if (descriptor != null) { 227 // Parse 228 try { 229 descriptor.parseRawDescriptors(stream); 230 231 // Clean up 232 descriptor.postParse(stream); 233 } catch (Exception ex) { 234 Log.e(TAG, "Exception parsing USB descriptors.", ex); 235 236 // Clean up 237 descriptor.setStatus(UsbDescriptor.STATUS_PARSE_EXCEPTION); 238 } finally { 239 mDescriptors.add(descriptor); 240 } 241 } 242 } 243 if (DEBUG) { 244 Log.d(TAG, "parseDescriptors() - end " + mDescriptors.size() + " descriptors."); 245 } 246 } 247 getRawDescriptors()248 public byte[] getRawDescriptors() { 249 return getRawDescriptors_native(mDeviceAddr); 250 } 251 getRawDescriptors_native(String deviceAddr)252 private native byte[] getRawDescriptors_native(String deviceAddr); 253 254 /** 255 * @hide 256 */ getDescriptorString(int stringId)257 public String getDescriptorString(int stringId) { 258 return getDescriptorString_native(mDeviceAddr, stringId); 259 } 260 getDescriptorString_native(String deviceAddr, int stringId)261 private native String getDescriptorString_native(String deviceAddr, int stringId); 262 getParsingSpec()263 public int getParsingSpec() { 264 return mDeviceDescriptor != null ? mDeviceDescriptor.getSpec() : 0; 265 } 266 getDescriptors()267 public ArrayList<UsbDescriptor> getDescriptors() { 268 return mDescriptors; 269 } 270 271 /** 272 * @hide 273 */ toAndroidUsbDevice()274 public UsbDevice.Builder toAndroidUsbDevice() { 275 if (mDeviceDescriptor == null) { 276 Log.e(TAG, "toAndroidUsbDevice() ERROR - No Device Descriptor"); 277 return null; 278 } 279 280 UsbDevice.Builder device = mDeviceDescriptor.toAndroid(this); 281 if (device == null) { 282 Log.e(TAG, "toAndroidUsbDevice() ERROR Creating Device"); 283 } 284 return device; 285 } 286 287 /** 288 * @hide 289 */ getDescriptors(byte type)290 public ArrayList<UsbDescriptor> getDescriptors(byte type) { 291 ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>(); 292 for (UsbDescriptor descriptor : mDescriptors) { 293 if (descriptor.getType() == type) { 294 list.add(descriptor); 295 } 296 } 297 return list; 298 } 299 300 /** 301 * @hide 302 */ getInterfaceDescriptorsForClass(int usbClass)303 public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(int usbClass) { 304 ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>(); 305 for (UsbDescriptor descriptor : mDescriptors) { 306 // ensure that this isn't an unrecognized DESCRIPTORTYPE_INTERFACE 307 if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_INTERFACE) { 308 if (descriptor instanceof UsbInterfaceDescriptor) { 309 UsbInterfaceDescriptor intrDesc = (UsbInterfaceDescriptor) descriptor; 310 if (intrDesc.getUsbClass() == usbClass) { 311 list.add(descriptor); 312 } 313 } else { 314 Log.w(TAG, "Unrecognized Interface l: " + descriptor.getLength() 315 + " t:0x" + Integer.toHexString(descriptor.getType())); 316 } 317 } 318 } 319 return list; 320 } 321 322 /** 323 * @hide 324 */ getACInterfaceDescriptors(byte subtype, int subclass)325 public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, int subclass) { 326 ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>(); 327 for (UsbDescriptor descriptor : mDescriptors) { 328 if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE) { 329 // ensure that this isn't an unrecognized DESCRIPTORTYPE_AUDIO_INTERFACE 330 if (descriptor instanceof UsbACInterface) { 331 UsbACInterface acDescriptor = (UsbACInterface) descriptor; 332 if (acDescriptor.getSubtype() == subtype 333 && acDescriptor.getSubclass() == subclass) { 334 list.add(descriptor); 335 } 336 } else { 337 Log.w(TAG, "Unrecognized Audio Interface l: " + descriptor.getLength() 338 + " t:0x" + Integer.toHexString(descriptor.getType())); 339 } 340 } 341 } 342 return list; 343 } 344 345 /* 346 * Attribute predicates 347 */ 348 /** 349 * @hide 350 */ hasInput()351 public boolean hasInput() { 352 if (DEBUG) { 353 Log.d(TAG, "---- hasInput()"); 354 } 355 ArrayList<UsbDescriptor> acDescriptors = 356 getACInterfaceDescriptors(UsbACInterface.ACI_INPUT_TERMINAL, 357 UsbACInterface.AUDIO_AUDIOCONTROL); 358 boolean hasInput = false; 359 for (UsbDescriptor descriptor : acDescriptors) { 360 if (descriptor instanceof UsbACTerminal) { 361 UsbACTerminal inDescr = (UsbACTerminal) descriptor; 362 // Check for input and bi-directional terminal types 363 int type = inDescr.getTerminalType(); 364 if (DEBUG) { 365 Log.d(TAG, " type:0x" + Integer.toHexString(type)); 366 } 367 int terminalCategory = type & ~0xFF; 368 if (terminalCategory != UsbTerminalTypes.TERMINAL_USB_UNDEFINED 369 && terminalCategory != UsbTerminalTypes.TERMINAL_OUT_UNDEFINED) { 370 // If not explicitly a USB connection or output, it could be an input. 371 hasInput = true; 372 break; 373 } 374 } else { 375 Log.w(TAG, "Undefined Audio Input terminal l: " + descriptor.getLength() 376 + " t:0x" + Integer.toHexString(descriptor.getType())); 377 } 378 } 379 380 if (DEBUG) { 381 Log.d(TAG, "hasInput() = " + hasInput); 382 } 383 return hasInput; 384 } 385 386 /** 387 * @hide 388 */ hasOutput()389 public boolean hasOutput() { 390 if (DEBUG) { 391 Log.d(TAG, "---- hasOutput()"); 392 } 393 ArrayList<UsbDescriptor> acDescriptors = 394 getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL, 395 UsbACInterface.AUDIO_AUDIOCONTROL); 396 boolean hasOutput = false; 397 for (UsbDescriptor descriptor : acDescriptors) { 398 if (descriptor instanceof UsbACTerminal) { 399 UsbACTerminal outDescr = (UsbACTerminal) descriptor; 400 // Check for output and bi-directional terminal types 401 int type = outDescr.getTerminalType(); 402 if (DEBUG) { 403 Log.d(TAG, " type:0x" + Integer.toHexString(type)); 404 } 405 int terminalCategory = type & ~0xFF; 406 if (terminalCategory != UsbTerminalTypes.TERMINAL_USB_UNDEFINED 407 && terminalCategory != UsbTerminalTypes.TERMINAL_IN_UNDEFINED) { 408 // If not explicitly a USB connection or input, it could be an output. 409 hasOutput = true; 410 break; 411 } 412 } else { 413 Log.w(TAG, "Undefined Audio Input terminal l: " + descriptor.getLength() 414 + " t:0x" + Integer.toHexString(descriptor.getType())); 415 } 416 } 417 if (DEBUG) { 418 Log.d(TAG, "hasOutput() = " + hasOutput); 419 } 420 return hasOutput; 421 } 422 423 /** 424 * @hide 425 */ hasMic()426 public boolean hasMic() { 427 boolean hasMic = false; 428 429 ArrayList<UsbDescriptor> acDescriptors = 430 getACInterfaceDescriptors(UsbACInterface.ACI_INPUT_TERMINAL, 431 UsbACInterface.AUDIO_AUDIOCONTROL); 432 for (UsbDescriptor descriptor : acDescriptors) { 433 if (descriptor instanceof UsbACTerminal) { 434 UsbACTerminal inDescr = (UsbACTerminal) descriptor; 435 if (inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_IN_MIC 436 || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET 437 || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_UNDEFINED 438 || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_EXTERN_LINE) { 439 hasMic = true; 440 break; 441 } 442 } else { 443 Log.w(TAG, "Undefined Audio Input terminal l: " + descriptor.getLength() 444 + " t:0x" + Integer.toHexString(descriptor.getType())); 445 } 446 } 447 return hasMic; 448 } 449 450 /** 451 * @hide 452 */ hasSpeaker()453 public boolean hasSpeaker() { 454 boolean hasSpeaker = false; 455 456 ArrayList<UsbDescriptor> acDescriptors = 457 getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL, 458 UsbACInterface.AUDIO_AUDIOCONTROL); 459 for (UsbDescriptor descriptor : acDescriptors) { 460 if (descriptor instanceof UsbACTerminal) { 461 UsbACTerminal outDescr = (UsbACTerminal) descriptor; 462 if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER 463 || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES 464 || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) { 465 hasSpeaker = true; 466 break; 467 } 468 } else { 469 Log.w(TAG, "Undefined Audio Output terminal l: " + descriptor.getLength() 470 + " t:0x" + Integer.toHexString(descriptor.getType())); 471 } 472 } 473 474 return hasSpeaker; 475 } 476 477 /** 478 *@ hide 479 */ hasAudioInterface()480 public boolean hasAudioInterface() { 481 ArrayList<UsbDescriptor> descriptors = 482 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO); 483 return !descriptors.isEmpty(); 484 } 485 486 /** 487 * @hide 488 */ hasHIDInterface()489 public boolean hasHIDInterface() { 490 ArrayList<UsbDescriptor> descriptors = 491 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_HID); 492 return !descriptors.isEmpty(); 493 } 494 495 /** 496 * @hide 497 */ hasStorageInterface()498 public boolean hasStorageInterface() { 499 ArrayList<UsbDescriptor> descriptors = 500 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_STORAGE); 501 return !descriptors.isEmpty(); 502 } 503 504 /** 505 * @hide 506 */ hasMIDIInterface()507 public boolean hasMIDIInterface() { 508 ArrayList<UsbDescriptor> descriptors = 509 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO); 510 for (UsbDescriptor descriptor : descriptors) { 511 // enusure that this isn't an unrecognized interface descriptor 512 if (descriptor instanceof UsbInterfaceDescriptor) { 513 UsbInterfaceDescriptor interfaceDescr = (UsbInterfaceDescriptor) descriptor; 514 if (interfaceDescr.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) { 515 return true; 516 } 517 } else { 518 Log.w(TAG, "Undefined Audio Class Interface l: " + descriptor.getLength() 519 + " t:0x" + Integer.toHexString(descriptor.getType())); 520 } 521 } 522 return false; 523 } 524 525 /** 526 * @hide 527 */ getInputHeadsetProbability()528 public float getInputHeadsetProbability() { 529 if (hasMIDIInterface()) { 530 return 0.0f; 531 } 532 533 float probability = 0.0f; 534 535 // Look for a microphone 536 boolean hasMic = hasMic(); 537 538 // Look for a "speaker" 539 boolean hasSpeaker = hasSpeaker(); 540 541 if (hasMic && hasSpeaker) { 542 probability += 0.75f; 543 } 544 545 if (hasMic && hasHIDInterface()) { 546 probability += 0.25f; 547 } 548 549 return probability; 550 } 551 552 /** 553 * getInputHeadsetProbability() reports a probability of a USB Input peripheral being a 554 * headset. The probability range is between 0.0f (definitely NOT a headset) and 555 * 1.0f (definitely IS a headset). A probability of 0.75f seems sufficient 556 * to count on the peripheral being a headset. 557 */ isInputHeadset()558 public boolean isInputHeadset() { 559 return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER; 560 } 561 562 /** 563 * @hide 564 */ getOutputHeadsetProbability()565 public float getOutputHeadsetProbability() { 566 if (hasMIDIInterface()) { 567 return 0.0f; 568 } 569 570 float probability = 0.0f; 571 ArrayList<UsbDescriptor> acDescriptors; 572 573 // Look for a "speaker" 574 boolean hasSpeaker = false; 575 acDescriptors = 576 getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL, 577 UsbACInterface.AUDIO_AUDIOCONTROL); 578 for (UsbDescriptor descriptor : acDescriptors) { 579 if (descriptor instanceof UsbACTerminal) { 580 UsbACTerminal outDescr = (UsbACTerminal) descriptor; 581 if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER 582 || outDescr.getTerminalType() 583 == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES 584 || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) { 585 hasSpeaker = true; 586 break; 587 } 588 } else { 589 Log.w(TAG, "Undefined Audio Output terminal l: " + descriptor.getLength() 590 + " t:0x" + Integer.toHexString(descriptor.getType())); 591 } 592 } 593 594 if (hasSpeaker) { 595 probability += 0.75f; 596 } 597 598 if (hasSpeaker && hasHIDInterface()) { 599 probability += 0.25f; 600 } 601 602 return probability; 603 } 604 605 /** 606 * getOutputHeadsetProbability() reports a probability of a USB Output peripheral being a 607 * headset. The probability range is between 0.0f (definitely NOT a headset) and 608 * 1.0f (definitely IS a headset). A probability of 0.75f seems sufficient 609 * to count on the peripheral being a headset. 610 */ isOutputHeadset()611 public boolean isOutputHeadset() { 612 return getOutputHeadsetProbability() >= OUT_HEADSET_TRIGGER; 613 } 614 615 } 616