1 /* 2 * Copyright (C) 2014 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 an 14 * limitations under the License. 15 */ 16 17 package com.android.server.usb; 18 19 import android.content.Context; 20 import android.content.pm.PackageManager; 21 import android.content.res.Resources; 22 import android.hardware.usb.UsbConstants; 23 import android.hardware.usb.UsbDevice; 24 import android.hardware.usb.UsbInterface; 25 import android.media.AudioSystem; 26 import android.media.IAudioService; 27 import android.media.midi.MidiDeviceInfo; 28 import android.os.Bundle; 29 import android.os.FileObserver; 30 import android.os.RemoteException; 31 import android.os.ServiceManager; 32 import android.os.SystemClock; 33 import android.provider.Settings; 34 import android.util.Slog; 35 36 import com.android.internal.alsa.AlsaCardsParser; 37 import com.android.internal.alsa.AlsaDevicesParser; 38 import com.android.internal.util.IndentingPrintWriter; 39 import com.android.server.audio.AudioService; 40 41 import libcore.io.IoUtils; 42 43 import java.io.File; 44 import java.util.HashMap; 45 46 /** 47 * UsbAlsaManager manages USB audio and MIDI devices. 48 */ 49 public final class UsbAlsaManager { 50 private static final String TAG = UsbAlsaManager.class.getSimpleName(); 51 private static final boolean DEBUG = false; 52 53 private static final String ALSA_DIRECTORY = "/dev/snd/"; 54 55 private final Context mContext; 56 private IAudioService mAudioService; 57 private final boolean mHasMidiFeature; 58 59 private final AlsaCardsParser mCardsParser = new AlsaCardsParser(); 60 private final AlsaDevicesParser mDevicesParser = new AlsaDevicesParser(); 61 62 // this is needed to map USB devices to ALSA Audio Devices, especially to remove an 63 // ALSA device when we are notified that its associated USB device has been removed. 64 65 private final HashMap<UsbDevice,UsbAudioDevice> 66 mAudioDevices = new HashMap<UsbDevice,UsbAudioDevice>(); 67 68 private boolean mIsInputHeadset; // as reported by UsbDescriptorParser 69 private boolean mIsOutputHeadset; // as reported by UsbDescriptorParser 70 71 private final HashMap<UsbDevice,UsbMidiDevice> 72 mMidiDevices = new HashMap<UsbDevice,UsbMidiDevice>(); 73 74 private final HashMap<String,AlsaDevice> 75 mAlsaDevices = new HashMap<String,AlsaDevice>(); 76 77 private UsbAudioDevice mAccessoryAudioDevice = null; 78 79 // UsbMidiDevice for USB peripheral mode (gadget) device 80 private UsbMidiDevice mPeripheralMidiDevice = null; 81 82 private final class AlsaDevice { 83 public static final int TYPE_UNKNOWN = 0; 84 public static final int TYPE_PLAYBACK = 1; 85 public static final int TYPE_CAPTURE = 2; 86 public static final int TYPE_MIDI = 3; 87 88 public int mCard; 89 public int mDevice; 90 public int mType; 91 AlsaDevice(int type, int card, int device)92 public AlsaDevice(int type, int card, int device) { 93 mType = type; 94 mCard = card; 95 mDevice = device; 96 } 97 equals(Object obj)98 public boolean equals(Object obj) { 99 if (! (obj instanceof AlsaDevice)) { 100 return false; 101 } 102 AlsaDevice other = (AlsaDevice)obj; 103 return (mType == other.mType && mCard == other.mCard && mDevice == other.mDevice); 104 } 105 toString()106 public String toString() { 107 StringBuilder sb = new StringBuilder(); 108 sb.append("AlsaDevice: [card: " + mCard); 109 sb.append(", device: " + mDevice); 110 sb.append(", type: " + mType); 111 sb.append("]"); 112 return sb.toString(); 113 } 114 } 115 116 private final FileObserver mAlsaObserver = new FileObserver(ALSA_DIRECTORY, 117 FileObserver.CREATE | FileObserver.DELETE) { 118 public void onEvent(int event, String path) { 119 switch (event) { 120 case FileObserver.CREATE: 121 alsaFileAdded(path); 122 break; 123 case FileObserver.DELETE: 124 alsaFileRemoved(path); 125 break; 126 } 127 } 128 }; 129 UsbAlsaManager(Context context)130 /* package */ UsbAlsaManager(Context context) { 131 mContext = context; 132 mHasMidiFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI); 133 134 // initial scan 135 mCardsParser.scan(); 136 } 137 systemReady()138 public void systemReady() { 139 mAudioService = IAudioService.Stub.asInterface( 140 ServiceManager.getService(Context.AUDIO_SERVICE)); 141 142 mAlsaObserver.startWatching(); 143 144 // add existing alsa devices 145 File[] files = new File(ALSA_DIRECTORY).listFiles(); 146 if (files != null) { 147 for (int i = 0; i < files.length; i++) { 148 alsaFileAdded(files[i].getName()); 149 } 150 } 151 } 152 153 // Notifies AudioService when a device is added or removed 154 // audioDevice - the AudioDevice that was added or removed 155 // enabled - if true, we're connecting a device (it's arrived), else disconnecting notifyDeviceState(UsbAudioDevice audioDevice, boolean enabled)156 private void notifyDeviceState(UsbAudioDevice audioDevice, boolean enabled) { 157 if (DEBUG) { 158 Slog.d(TAG, "notifyDeviceState " + enabled + " " + audioDevice); 159 } 160 161 if (mAudioService == null) { 162 Slog.e(TAG, "no AudioService"); 163 return; 164 } 165 166 // FIXME Does not yet handle the case where the setting is changed 167 // after device connection. Ideally we should handle the settings change 168 // in SettingsObserver. Here we should log that a USB device is connected 169 // and disconnected with its address (card , device) and force the 170 // connection or disconnection when the setting changes. 171 int isDisabled = Settings.Secure.getInt(mContext.getContentResolver(), 172 Settings.Secure.USB_AUDIO_AUTOMATIC_ROUTING_DISABLED, 0); 173 if (isDisabled != 0) { 174 return; 175 } 176 177 int state = (enabled ? 1 : 0); 178 int alsaCard = audioDevice.mCard; 179 int alsaDevice = audioDevice.mDevice; 180 if (alsaCard < 0 || alsaDevice < 0) { 181 Slog.e(TAG, "Invalid alsa card or device alsaCard: " + alsaCard + 182 " alsaDevice: " + alsaDevice); 183 return; 184 } 185 186 String address = AudioService.makeAlsaAddressString(alsaCard, alsaDevice); 187 try { 188 // Playback Device 189 if (audioDevice.mHasPlayback) { 190 int device; 191 if (mIsOutputHeadset) { 192 device = AudioSystem.DEVICE_OUT_USB_HEADSET; 193 } else { 194 device = (audioDevice == mAccessoryAudioDevice 195 ? AudioSystem.DEVICE_OUT_USB_ACCESSORY 196 : AudioSystem.DEVICE_OUT_USB_DEVICE); 197 } 198 if (DEBUG) { 199 Slog.i(TAG, "pre-call device:0x" + Integer.toHexString(device) + 200 " addr:" + address + " name:" + audioDevice.getDeviceName()); 201 } 202 mAudioService.setWiredDeviceConnectionState( 203 device, state, address, audioDevice.getDeviceName(), TAG); 204 } 205 206 // Capture Device 207 if (audioDevice.mHasCapture) { 208 int device; 209 if (mIsInputHeadset) { 210 device = AudioSystem.DEVICE_IN_USB_HEADSET; 211 } else { 212 device = (audioDevice == mAccessoryAudioDevice 213 ? AudioSystem.DEVICE_IN_USB_ACCESSORY 214 : AudioSystem.DEVICE_IN_USB_DEVICE); 215 } 216 mAudioService.setWiredDeviceConnectionState( 217 device, state, address, audioDevice.getDeviceName(), TAG); 218 } 219 } catch (RemoteException e) { 220 Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState"); 221 } 222 } 223 waitForAlsaDevice(int card, int device, int type)224 private AlsaDevice waitForAlsaDevice(int card, int device, int type) { 225 if (DEBUG) { 226 Slog.e(TAG, "waitForAlsaDevice(c:" + card + " d:" + device + ")"); 227 } 228 229 AlsaDevice testDevice = new AlsaDevice(type, card, device); 230 231 // This value was empirically determined. 232 final int kWaitTimeMs = 2500; 233 234 synchronized(mAlsaDevices) { 235 long timeoutMs = SystemClock.elapsedRealtime() + kWaitTimeMs; 236 do { 237 if (mAlsaDevices.values().contains(testDevice)) { 238 return testDevice; 239 } 240 long waitTimeMs = timeoutMs - SystemClock.elapsedRealtime(); 241 if (waitTimeMs > 0) { 242 try { 243 mAlsaDevices.wait(waitTimeMs); 244 } catch (InterruptedException e) { 245 Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file."); 246 } 247 } 248 } while (timeoutMs > SystemClock.elapsedRealtime()); 249 } 250 251 Slog.e(TAG, "waitForAlsaDevice failed for " + testDevice); 252 return null; 253 } 254 alsaFileAdded(String name)255 private void alsaFileAdded(String name) { 256 int type = AlsaDevice.TYPE_UNKNOWN; 257 int card = -1, device = -1; 258 259 if (name.startsWith("pcmC")) { 260 if (name.endsWith("p")) { 261 type = AlsaDevice.TYPE_PLAYBACK; 262 } else if (name.endsWith("c")) { 263 type = AlsaDevice.TYPE_CAPTURE; 264 } 265 } else if (name.startsWith("midiC")) { 266 type = AlsaDevice.TYPE_MIDI; 267 } 268 269 if (type != AlsaDevice.TYPE_UNKNOWN) { 270 try { 271 int c_index = name.indexOf('C'); 272 int d_index = name.indexOf('D'); 273 int end = name.length(); 274 if (type == AlsaDevice.TYPE_PLAYBACK || type == AlsaDevice.TYPE_CAPTURE) { 275 // skip trailing 'p' or 'c' 276 end--; 277 } 278 card = Integer.parseInt(name.substring(c_index + 1, d_index)); 279 device = Integer.parseInt(name.substring(d_index + 1, end)); 280 } catch (Exception e) { 281 Slog.e(TAG, "Could not parse ALSA file name " + name, e); 282 return; 283 } 284 synchronized(mAlsaDevices) { 285 if (mAlsaDevices.get(name) == null) { 286 AlsaDevice alsaDevice = new AlsaDevice(type, card, device); 287 Slog.d(TAG, "Adding ALSA device " + alsaDevice); 288 mAlsaDevices.put(name, alsaDevice); 289 mAlsaDevices.notifyAll(); 290 } 291 } 292 } 293 } 294 alsaFileRemoved(String path)295 private void alsaFileRemoved(String path) { 296 synchronized(mAlsaDevices) { 297 AlsaDevice device = mAlsaDevices.remove(path); 298 if (device != null) { 299 Slog.d(TAG, "ALSA device removed: " + device); 300 } 301 } 302 } 303 304 /* 305 * Select the default device of the specified card. 306 */ selectAudioCard(int card)307 /* package */ UsbAudioDevice selectAudioCard(int card) { 308 if (DEBUG) { 309 Slog.d(TAG, "selectAudioCard() card:" + card 310 + " isCardUsb(): " + mCardsParser.isCardUsb(card)); 311 } 312 if (!mCardsParser.isCardUsb(card)) { 313 // Don't. AudioPolicyManager has logic for falling back to internal devices. 314 return null; 315 } 316 317 mDevicesParser.scan(); 318 int device = mDevicesParser.getDefaultDeviceNum(card); 319 320 boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card); 321 boolean hasCapture = mDevicesParser.hasCaptureDevices(card); 322 if (DEBUG) { 323 Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture); 324 } 325 326 int deviceClass = 327 (mCardsParser.isCardUsb(card) 328 ? UsbAudioDevice.kAudioDeviceClass_External 329 : UsbAudioDevice.kAudioDeviceClass_Internal) | 330 UsbAudioDevice.kAudioDeviceMeta_Alsa; 331 332 // Playback device file needed/present? 333 if (hasPlayback && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_PLAYBACK) == null)) { 334 return null; 335 } 336 337 // Capture device file needed/present? 338 if (hasCapture && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_CAPTURE) == null)) { 339 return null; 340 } 341 342 UsbAudioDevice audioDevice = 343 new UsbAudioDevice(card, device, hasPlayback, hasCapture, deviceClass); 344 AlsaCardsParser.AlsaCardRecord cardRecord = mCardsParser.getCardRecordFor(card); 345 audioDevice.setDeviceNameAndDescription(cardRecord.mCardName, cardRecord.mCardDescription); 346 347 notifyDeviceState(audioDevice, true /*enabled*/); 348 349 return audioDevice; 350 } 351 selectDefaultDevice()352 /* package */ UsbAudioDevice selectDefaultDevice() { 353 if (DEBUG) { 354 Slog.d(TAG, "UsbAudioManager.selectDefaultDevice()"); 355 } 356 return selectAudioCard(mCardsParser.getDefaultCard()); 357 } 358 usbDeviceAdded(UsbDevice usbDevice, boolean isInputHeadset, boolean isOutputHeadset)359 /* package */ void usbDeviceAdded(UsbDevice usbDevice, 360 boolean isInputHeadset, boolean isOutputHeadset) { 361 if (DEBUG) { 362 Slog.d(TAG, "deviceAdded(): " + usbDevice.getManufacturerName() 363 + " nm:" + usbDevice.getProductName()); 364 } 365 366 mIsInputHeadset = isInputHeadset; 367 mIsOutputHeadset = isOutputHeadset; 368 369 // Is there an audio interface in there? 370 boolean isAudioDevice = false; 371 372 // FIXME - handle multiple configurations? 373 int interfaceCount = usbDevice.getInterfaceCount(); 374 for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < interfaceCount; 375 ntrfaceIndex++) { 376 UsbInterface ntrface = usbDevice.getInterface(ntrfaceIndex); 377 if (ntrface.getInterfaceClass() == UsbConstants.USB_CLASS_AUDIO) { 378 isAudioDevice = true; 379 } 380 } 381 382 if (DEBUG) { 383 Slog.d(TAG, " isAudioDevice: " + isAudioDevice); 384 } 385 if (!isAudioDevice) { 386 return; 387 } 388 389 int addedCard = mCardsParser.getDefaultUsbCard(); 390 391 // If the default isn't a USB device, let the existing "select internal mechanism" 392 // handle the selection. 393 if (DEBUG) { 394 Slog.d(TAG, " mCardsParser.isCardUsb(" + addedCard + ") = " 395 + mCardsParser.isCardUsb(addedCard)); 396 } 397 if (mCardsParser.isCardUsb(addedCard)) { 398 UsbAudioDevice audioDevice = selectAudioCard(addedCard); 399 if (audioDevice != null) { 400 mAudioDevices.put(usbDevice, audioDevice); 401 Slog.i(TAG, "USB Audio Device Added: " + audioDevice); 402 } 403 404 // look for MIDI devices 405 406 // Don't need to call mDevicesParser.scan() because selectAudioCard() does this above. 407 // Uncomment this next line if that behavior changes in the fugure. 408 // mDevicesParser.scan() 409 410 boolean hasMidi = mDevicesParser.hasMIDIDevices(addedCard); 411 if (hasMidi && mHasMidiFeature) { 412 int device = mDevicesParser.getDefaultDeviceNum(addedCard); 413 AlsaDevice alsaDevice = waitForAlsaDevice(addedCard, device, AlsaDevice.TYPE_MIDI); 414 if (alsaDevice != null) { 415 Bundle properties = new Bundle(); 416 String manufacturer = usbDevice.getManufacturerName(); 417 String product = usbDevice.getProductName(); 418 String version = usbDevice.getVersion(); 419 String name; 420 if (manufacturer == null || manufacturer.isEmpty()) { 421 name = product; 422 } else if (product == null || product.isEmpty()) { 423 name = manufacturer; 424 } else { 425 name = manufacturer + " " + product; 426 } 427 properties.putString(MidiDeviceInfo.PROPERTY_NAME, name); 428 properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer); 429 properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product); 430 properties.putString(MidiDeviceInfo.PROPERTY_VERSION, version); 431 properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER, 432 usbDevice.getSerialNumber()); 433 properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, alsaDevice.mCard); 434 properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, alsaDevice.mDevice); 435 properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice); 436 437 UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties, 438 alsaDevice.mCard, alsaDevice.mDevice); 439 if (usbMidiDevice != null) { 440 mMidiDevices.put(usbDevice, usbMidiDevice); 441 } 442 } 443 } 444 } 445 446 if (DEBUG) { 447 Slog.d(TAG, "deviceAdded() - done"); 448 } 449 } 450 usbDeviceRemoved(UsbDevice usbDevice)451 /* package */ void usbDeviceRemoved(UsbDevice usbDevice) { 452 if (DEBUG) { 453 Slog.d(TAG, "deviceRemoved(): " + usbDevice.getManufacturerName() + 454 " " + usbDevice.getProductName()); 455 } 456 457 UsbAudioDevice audioDevice = mAudioDevices.remove(usbDevice); 458 Slog.i(TAG, "USB Audio Device Removed: " + audioDevice); 459 if (audioDevice != null) { 460 if (audioDevice.mHasPlayback || audioDevice.mHasCapture) { 461 notifyDeviceState(audioDevice, false /*enabled*/); 462 463 // if there any external devices left, select one of them 464 selectDefaultDevice(); 465 } 466 } 467 UsbMidiDevice usbMidiDevice = mMidiDevices.remove(usbDevice); 468 if (usbMidiDevice != null) { 469 IoUtils.closeQuietly(usbMidiDevice); 470 } 471 } 472 setAccessoryAudioState(boolean enabled, int card, int device)473 /* package */ void setAccessoryAudioState(boolean enabled, int card, int device) { 474 if (DEBUG) { 475 Slog.d(TAG, "setAccessoryAudioState " + enabled + " " + card + " " + device); 476 } 477 if (enabled) { 478 mAccessoryAudioDevice = new UsbAudioDevice(card, device, true, false, 479 UsbAudioDevice.kAudioDeviceClass_External); 480 notifyDeviceState(mAccessoryAudioDevice, true /*enabled*/); 481 } else if (mAccessoryAudioDevice != null) { 482 notifyDeviceState(mAccessoryAudioDevice, false /*enabled*/); 483 mAccessoryAudioDevice = null; 484 } 485 } 486 setPeripheralMidiState(boolean enabled, int card, int device)487 /* package */ void setPeripheralMidiState(boolean enabled, int card, int device) { 488 if (!mHasMidiFeature) { 489 return; 490 } 491 492 if (enabled && mPeripheralMidiDevice == null) { 493 Bundle properties = new Bundle(); 494 Resources r = mContext.getResources(); 495 properties.putString(MidiDeviceInfo.PROPERTY_NAME, r.getString( 496 com.android.internal.R.string.usb_midi_peripheral_name)); 497 properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, r.getString( 498 com.android.internal.R.string.usb_midi_peripheral_manufacturer_name)); 499 properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, r.getString( 500 com.android.internal.R.string.usb_midi_peripheral_product_name)); 501 properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card); 502 properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device); 503 mPeripheralMidiDevice = UsbMidiDevice.create(mContext, properties, card, device); 504 } else if (!enabled && mPeripheralMidiDevice != null) { 505 IoUtils.closeQuietly(mPeripheralMidiDevice); 506 mPeripheralMidiDevice = null; 507 } 508 } 509 510 // 511 // Devices List 512 // 513 /* 514 //import java.util.ArrayList; 515 public ArrayList<UsbAudioDevice> getConnectedDevices() { 516 ArrayList<UsbAudioDevice> devices = new ArrayList<UsbAudioDevice>(mAudioDevices.size()); 517 for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) { 518 devices.add(entry.getValue()); 519 } 520 return devices; 521 } 522 */ 523 524 // 525 // Logging 526 // 527 // called by UsbService.dump dump(IndentingPrintWriter pw)528 public void dump(IndentingPrintWriter pw) { 529 pw.println("USB Audio Devices:"); 530 for (UsbDevice device : mAudioDevices.keySet()) { 531 pw.println(" " + device.getDeviceName() + ": " + mAudioDevices.get(device)); 532 } 533 pw.println("USB MIDI Devices:"); 534 for (UsbDevice device : mMidiDevices.keySet()) { 535 pw.println(" " + device.getDeviceName() + ": " + mMidiDevices.get(device)); 536 } 537 } 538 539 /* 540 public void logDevicesList(String title) { 541 if (DEBUG) { 542 for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) { 543 Slog.i(TAG, "UsbDevice-------------------"); 544 Slog.i(TAG, "" + (entry != null ? entry.getKey() : "[none]")); 545 Slog.i(TAG, "UsbAudioDevice--------------"); 546 Slog.i(TAG, "" + entry.getValue()); 547 } 548 } 549 } 550 */ 551 552 // This logs a more terse (and more readable) version of the devices list 553 /* 554 public void logDevices(String title) { 555 if (DEBUG) { 556 Slog.i(TAG, title); 557 for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) { 558 Slog.i(TAG, entry.getValue().toShortString()); 559 } 560 } 561 } 562 */ 563 564 } 565