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.FileObserver; 29 import android.os.Bundle; 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.io.FileDescriptor; 45 import java.io.PrintWriter; 46 import java.util.HashMap; 47 import java.util.ArrayList; 48 49 /** 50 * UsbAlsaManager manages USB audio and MIDI devices. 51 */ 52 public final class UsbAlsaManager { 53 private static final String TAG = UsbAlsaManager.class.getSimpleName(); 54 private static final boolean DEBUG = false; 55 56 private static final String ALSA_DIRECTORY = "/dev/snd/"; 57 58 private final Context mContext; 59 private IAudioService mAudioService; 60 private final boolean mHasMidiFeature; 61 62 private final AlsaCardsParser mCardsParser = new AlsaCardsParser(); 63 private final AlsaDevicesParser mDevicesParser = new AlsaDevicesParser(); 64 65 // this is needed to map USB devices to ALSA Audio Devices, especially to remove an 66 // ALSA device when we are notified that its associated USB device has been removed. 67 68 private final HashMap<UsbDevice,UsbAudioDevice> 69 mAudioDevices = new HashMap<UsbDevice,UsbAudioDevice>(); 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 = (audioDevice == mAccessoryAudioDevice ? 191 AudioSystem.DEVICE_OUT_USB_ACCESSORY : 192 AudioSystem.DEVICE_OUT_USB_DEVICE); 193 if (DEBUG) { 194 Slog.i(TAG, "pre-call device:0x" + Integer.toHexString(device) + 195 " addr:" + address + " name:" + audioDevice.mDeviceName); 196 } 197 mAudioService.setWiredDeviceConnectionState( 198 device, state, address, audioDevice.mDeviceName, TAG); 199 } 200 201 // Capture Device 202 if (audioDevice.mHasCapture) { 203 int device = (audioDevice == mAccessoryAudioDevice ? 204 AudioSystem.DEVICE_IN_USB_ACCESSORY : 205 AudioSystem.DEVICE_IN_USB_DEVICE); 206 mAudioService.setWiredDeviceConnectionState( 207 device, state, address, audioDevice.mDeviceName, TAG); 208 } 209 } catch (RemoteException e) { 210 Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState"); 211 } 212 } 213 waitForAlsaDevice(int card, int device, int type)214 private AlsaDevice waitForAlsaDevice(int card, int device, int type) { 215 if (DEBUG) { 216 Slog.e(TAG, "waitForAlsaDevice(c:" + card + " d:" + device + ")"); 217 } 218 219 AlsaDevice testDevice = new AlsaDevice(type, card, device); 220 221 // This value was empirically determined. 222 final int kWaitTime = 2500; // ms 223 224 synchronized(mAlsaDevices) { 225 long timeout = SystemClock.elapsedRealtime() + kWaitTime; 226 do { 227 if (mAlsaDevices.values().contains(testDevice)) { 228 return testDevice; 229 } 230 long waitTime = timeout - SystemClock.elapsedRealtime(); 231 if (waitTime > 0) { 232 try { 233 mAlsaDevices.wait(waitTime); 234 } catch (InterruptedException e) { 235 Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file."); 236 } 237 } 238 } while (timeout > SystemClock.elapsedRealtime()); 239 } 240 241 Slog.e(TAG, "waitForAlsaDevice failed for " + testDevice); 242 return null; 243 } 244 alsaFileAdded(String name)245 private void alsaFileAdded(String name) { 246 int type = AlsaDevice.TYPE_UNKNOWN; 247 int card = -1, device = -1; 248 249 if (name.startsWith("pcmC")) { 250 if (name.endsWith("p")) { 251 type = AlsaDevice.TYPE_PLAYBACK; 252 } else if (name.endsWith("c")) { 253 type = AlsaDevice.TYPE_CAPTURE; 254 } 255 } else if (name.startsWith("midiC")) { 256 type = AlsaDevice.TYPE_MIDI; 257 } 258 259 if (type != AlsaDevice.TYPE_UNKNOWN) { 260 try { 261 int c_index = name.indexOf('C'); 262 int d_index = name.indexOf('D'); 263 int end = name.length(); 264 if (type == AlsaDevice.TYPE_PLAYBACK || type == AlsaDevice.TYPE_CAPTURE) { 265 // skip trailing 'p' or 'c' 266 end--; 267 } 268 card = Integer.parseInt(name.substring(c_index + 1, d_index)); 269 device = Integer.parseInt(name.substring(d_index + 1, end)); 270 } catch (Exception e) { 271 Slog.e(TAG, "Could not parse ALSA file name " + name, e); 272 return; 273 } 274 synchronized(mAlsaDevices) { 275 if (mAlsaDevices.get(name) == null) { 276 AlsaDevice alsaDevice = new AlsaDevice(type, card, device); 277 Slog.d(TAG, "Adding ALSA device " + alsaDevice); 278 mAlsaDevices.put(name, alsaDevice); 279 mAlsaDevices.notifyAll(); 280 } 281 } 282 } 283 } 284 alsaFileRemoved(String path)285 private void alsaFileRemoved(String path) { 286 synchronized(mAlsaDevices) { 287 AlsaDevice device = mAlsaDevices.remove(path); 288 if (device != null) { 289 Slog.d(TAG, "ALSA device removed: " + device); 290 } 291 } 292 } 293 294 /* 295 * Select the default device of the specified card. 296 */ selectAudioCard(int card)297 /* package */ UsbAudioDevice selectAudioCard(int card) { 298 if (DEBUG) { 299 Slog.d(TAG, "selectAudioCard() card:" + card 300 + " isCardUsb(): " + mCardsParser.isCardUsb(card)); 301 } 302 if (!mCardsParser.isCardUsb(card)) { 303 // Don't. AudioPolicyManager has logic for falling back to internal devices. 304 return null; 305 } 306 307 mDevicesParser.scan(); 308 int device = mDevicesParser.getDefaultDeviceNum(card); 309 310 boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card); 311 boolean hasCapture = mDevicesParser.hasCaptureDevices(card); 312 if (DEBUG) { 313 Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture); 314 } 315 316 int deviceClass = 317 (mCardsParser.isCardUsb(card) 318 ? UsbAudioDevice.kAudioDeviceClass_External 319 : UsbAudioDevice.kAudioDeviceClass_Internal) | 320 UsbAudioDevice.kAudioDeviceMeta_Alsa; 321 322 // Playback device file needed/present? 323 if (hasPlayback && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_PLAYBACK) == null)) { 324 return null; 325 } 326 327 // Capture device file needed/present? 328 if (hasCapture && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_CAPTURE) == null)) { 329 return null; 330 } 331 332 UsbAudioDevice audioDevice = 333 new UsbAudioDevice(card, device, hasPlayback, hasCapture, deviceClass); 334 AlsaCardsParser.AlsaCardRecord cardRecord = mCardsParser.getCardRecordFor(card); 335 audioDevice.mDeviceName = cardRecord.mCardName; 336 audioDevice.mDeviceDescription = cardRecord.mCardDescription; 337 338 notifyDeviceState(audioDevice, true); 339 340 return audioDevice; 341 } 342 selectDefaultDevice()343 /* package */ UsbAudioDevice selectDefaultDevice() { 344 if (DEBUG) { 345 Slog.d(TAG, "UsbAudioManager.selectDefaultDevice()"); 346 } 347 return selectAudioCard(mCardsParser.getDefaultCard()); 348 } 349 usbDeviceAdded(UsbDevice usbDevice)350 /* package */ void usbDeviceAdded(UsbDevice usbDevice) { 351 if (DEBUG) { 352 Slog.d(TAG, "deviceAdded(): " + usbDevice.getManufacturerName() + 353 " nm:" + usbDevice.getProductName()); 354 } 355 356 // Is there an audio interface in there? 357 boolean isAudioDevice = false; 358 359 // FIXME - handle multiple configurations? 360 int interfaceCount = usbDevice.getInterfaceCount(); 361 for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < interfaceCount; 362 ntrfaceIndex++) { 363 UsbInterface ntrface = usbDevice.getInterface(ntrfaceIndex); 364 if (ntrface.getInterfaceClass() == UsbConstants.USB_CLASS_AUDIO) { 365 isAudioDevice = true; 366 } 367 } 368 369 if (DEBUG) { 370 Slog.d(TAG, " isAudioDevice: " + isAudioDevice); 371 } 372 if (!isAudioDevice) { 373 return; 374 } 375 376 int addedCard = mCardsParser.getDefaultUsbCard(); 377 378 // If the default isn't a USB device, let the existing "select internal mechanism" 379 // handle the selection. 380 if (DEBUG) { 381 Slog.d(TAG, " mCardsParser.isCardUsb(" + addedCard + ") = " 382 + mCardsParser.isCardUsb(addedCard)); 383 } 384 if (mCardsParser.isCardUsb(addedCard)) { 385 UsbAudioDevice audioDevice = selectAudioCard(addedCard); 386 if (audioDevice != null) { 387 mAudioDevices.put(usbDevice, audioDevice); 388 Slog.i(TAG, "USB Audio Device Added: " + audioDevice); 389 } 390 391 // look for MIDI devices 392 393 // Don't need to call mDevicesParser.scan() because selectAudioCard() does this above. 394 // Uncomment this next line if that behavior changes in the fugure. 395 // mDevicesParser.scan() 396 397 boolean hasMidi = mDevicesParser.hasMIDIDevices(addedCard); 398 if (hasMidi && mHasMidiFeature) { 399 int device = mDevicesParser.getDefaultDeviceNum(addedCard); 400 AlsaDevice alsaDevice = waitForAlsaDevice(addedCard, device, AlsaDevice.TYPE_MIDI); 401 if (alsaDevice != null) { 402 Bundle properties = new Bundle(); 403 String manufacturer = usbDevice.getManufacturerName(); 404 String product = usbDevice.getProductName(); 405 String version = usbDevice.getVersion(); 406 String name; 407 if (manufacturer == null || manufacturer.isEmpty()) { 408 name = product; 409 } else if (product == null || product.isEmpty()) { 410 name = manufacturer; 411 } else { 412 name = manufacturer + " " + product; 413 } 414 properties.putString(MidiDeviceInfo.PROPERTY_NAME, name); 415 properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer); 416 properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product); 417 properties.putString(MidiDeviceInfo.PROPERTY_VERSION, version); 418 properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER, 419 usbDevice.getSerialNumber()); 420 properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, alsaDevice.mCard); 421 properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, alsaDevice.mDevice); 422 properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice); 423 424 UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties, 425 alsaDevice.mCard, alsaDevice.mDevice); 426 if (usbMidiDevice != null) { 427 mMidiDevices.put(usbDevice, usbMidiDevice); 428 } 429 } 430 } 431 } 432 433 if (DEBUG) { 434 Slog.d(TAG, "deviceAdded() - done"); 435 } 436 } 437 usbDeviceRemoved(UsbDevice usbDevice)438 /* package */ void usbDeviceRemoved(UsbDevice usbDevice) { 439 if (DEBUG) { 440 Slog.d(TAG, "deviceRemoved(): " + usbDevice.getManufacturerName() + 441 " " + usbDevice.getProductName()); 442 } 443 444 UsbAudioDevice audioDevice = mAudioDevices.remove(usbDevice); 445 Slog.i(TAG, "USB Audio Device Removed: " + audioDevice); 446 if (audioDevice != null) { 447 if (audioDevice.mHasPlayback || audioDevice.mHasCapture) { 448 notifyDeviceState(audioDevice, false); 449 450 // if there any external devices left, select one of them 451 selectDefaultDevice(); 452 } 453 } 454 UsbMidiDevice usbMidiDevice = mMidiDevices.remove(usbDevice); 455 if (usbMidiDevice != null) { 456 IoUtils.closeQuietly(usbMidiDevice); 457 } 458 } 459 setAccessoryAudioState(boolean enabled, int card, int device)460 /* package */ void setAccessoryAudioState(boolean enabled, int card, int device) { 461 if (DEBUG) { 462 Slog.d(TAG, "setAccessoryAudioState " + enabled + " " + card + " " + device); 463 } 464 if (enabled) { 465 mAccessoryAudioDevice = new UsbAudioDevice(card, device, true, false, 466 UsbAudioDevice.kAudioDeviceClass_External); 467 notifyDeviceState(mAccessoryAudioDevice, true); 468 } else if (mAccessoryAudioDevice != null) { 469 notifyDeviceState(mAccessoryAudioDevice, false); 470 mAccessoryAudioDevice = null; 471 } 472 } 473 setPeripheralMidiState(boolean enabled, int card, int device)474 /* package */ void setPeripheralMidiState(boolean enabled, int card, int device) { 475 if (!mHasMidiFeature) { 476 return; 477 } 478 479 if (enabled && mPeripheralMidiDevice == null) { 480 Bundle properties = new Bundle(); 481 Resources r = mContext.getResources(); 482 properties.putString(MidiDeviceInfo.PROPERTY_NAME, r.getString( 483 com.android.internal.R.string.usb_midi_peripheral_name)); 484 properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, r.getString( 485 com.android.internal.R.string.usb_midi_peripheral_manufacturer_name)); 486 properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, r.getString( 487 com.android.internal.R.string.usb_midi_peripheral_product_name)); 488 properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card); 489 properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device); 490 mPeripheralMidiDevice = UsbMidiDevice.create(mContext, properties, card, device); 491 } else if (!enabled && mPeripheralMidiDevice != null) { 492 IoUtils.closeQuietly(mPeripheralMidiDevice); 493 mPeripheralMidiDevice = null; 494 } 495 } 496 497 // 498 // Devices List 499 // getConnectedDevices()500 public ArrayList<UsbAudioDevice> getConnectedDevices() { 501 ArrayList<UsbAudioDevice> devices = new ArrayList<UsbAudioDevice>(mAudioDevices.size()); 502 for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) { 503 devices.add(entry.getValue()); 504 } 505 return devices; 506 } 507 508 // 509 // Logging 510 // dump(IndentingPrintWriter pw)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 logDevicesList(String title)522 public void logDevicesList(String title) { 523 if (DEBUG) { 524 for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) { 525 Slog.i(TAG, "UsbDevice-------------------"); 526 Slog.i(TAG, "" + (entry != null ? entry.getKey() : "[none]")); 527 Slog.i(TAG, "UsbAudioDevice--------------"); 528 Slog.i(TAG, "" + entry.getValue()); 529 } 530 } 531 } 532 533 // This logs a more terse (and more readable) version of the devices list logDevices(String title)534 public void logDevices(String title) { 535 if (DEBUG) { 536 Slog.i(TAG, title); 537 for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) { 538 Slog.i(TAG, entry.getValue().toShortString()); 539 } 540 } 541 } 542 } 543