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