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 92 public AlsaDevice(int type, int card, int device) { 93 mType = type; 94 mCard = card; 95 mDevice = device; 96 } 97 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 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 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 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 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 214 private AlsaDevice waitForAlsaDevice(int card, int device, int type) { 215 AlsaDevice testDevice = new AlsaDevice(type, card, device); 216 217 // This value was empirically determined. 218 final int kWaitTime = 2500; // ms 219 220 synchronized(mAlsaDevices) { 221 long timeout = SystemClock.elapsedRealtime() + kWaitTime; 222 do { 223 if (mAlsaDevices.values().contains(testDevice)) { 224 return testDevice; 225 } 226 long waitTime = timeout - SystemClock.elapsedRealtime(); 227 if (waitTime > 0) { 228 try { 229 mAlsaDevices.wait(waitTime); 230 } catch (InterruptedException e) { 231 Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file."); 232 } 233 } 234 } while (timeout > SystemClock.elapsedRealtime()); 235 } 236 237 Slog.e(TAG, "waitForAlsaDevice failed for " + testDevice); 238 return null; 239 } 240 241 private void alsaFileAdded(String name) { 242 int type = AlsaDevice.TYPE_UNKNOWN; 243 int card = -1, device = -1; 244 245 if (name.startsWith("pcmC")) { 246 if (name.endsWith("p")) { 247 type = AlsaDevice.TYPE_PLAYBACK; 248 } else if (name.endsWith("c")) { 249 type = AlsaDevice.TYPE_CAPTURE; 250 } 251 } else if (name.startsWith("midiC")) { 252 type = AlsaDevice.TYPE_MIDI; 253 } 254 255 if (type != AlsaDevice.TYPE_UNKNOWN) { 256 try { 257 int c_index = name.indexOf('C'); 258 int d_index = name.indexOf('D'); 259 int end = name.length(); 260 if (type == AlsaDevice.TYPE_PLAYBACK || type == AlsaDevice.TYPE_CAPTURE) { 261 // skip trailing 'p' or 'c' 262 end--; 263 } 264 card = Integer.parseInt(name.substring(c_index + 1, d_index)); 265 device = Integer.parseInt(name.substring(d_index + 1, end)); 266 } catch (Exception e) { 267 Slog.e(TAG, "Could not parse ALSA file name " + name, e); 268 return; 269 } 270 synchronized(mAlsaDevices) { 271 if (mAlsaDevices.get(name) == null) { 272 AlsaDevice alsaDevice = new AlsaDevice(type, card, device); 273 Slog.d(TAG, "Adding ALSA device " + alsaDevice); 274 mAlsaDevices.put(name, alsaDevice); 275 mAlsaDevices.notifyAll(); 276 } 277 } 278 } 279 } 280 281 private void alsaFileRemoved(String path) { 282 synchronized(mAlsaDevices) { 283 AlsaDevice device = mAlsaDevices.remove(path); 284 if (device != null) { 285 Slog.d(TAG, "ALSA device removed: " + device); 286 } 287 } 288 } 289 290 /* 291 * Select the default device of the specified card. 292 */ 293 /* package */ UsbAudioDevice selectAudioCard(int card) { 294 if (DEBUG) { 295 Slog.d(TAG, "selectAudioCard() card:" + card); 296 } 297 if (!mCardsParser.isCardUsb(card)) { 298 // Don't. AudioPolicyManager has logic for falling back to internal devices. 299 return null; 300 } 301 302 mDevicesParser.scan(); 303 int device = mDevicesParser.getDefaultDeviceNum(card); 304 305 boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card); 306 boolean hasCapture = mDevicesParser.hasCaptureDevices(card); 307 int deviceClass = 308 (mCardsParser.isCardUsb(card) 309 ? UsbAudioDevice.kAudioDeviceClass_External 310 : UsbAudioDevice.kAudioDeviceClass_Internal) | 311 UsbAudioDevice.kAudioDeviceMeta_Alsa; 312 313 // Playback device file needed/present? 314 if (hasPlayback && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_PLAYBACK) == null)) { 315 return null; 316 } 317 318 // Capture device file needed/present? 319 if (hasCapture && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_CAPTURE) == null)) { 320 return null; 321 } 322 323 if (DEBUG) { 324 Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture); 325 } 326 327 UsbAudioDevice audioDevice = 328 new UsbAudioDevice(card, device, hasPlayback, hasCapture, deviceClass); 329 AlsaCardsParser.AlsaCardRecord cardRecord = mCardsParser.getCardRecordFor(card); 330 audioDevice.mDeviceName = cardRecord.mCardName; 331 audioDevice.mDeviceDescription = cardRecord.mCardDescription; 332 333 notifyDeviceState(audioDevice, true); 334 335 return audioDevice; 336 } 337 338 /* package */ UsbAudioDevice selectDefaultDevice() { 339 if (DEBUG) { 340 Slog.d(TAG, "UsbAudioManager.selectDefaultDevice()"); 341 } 342 mCardsParser.scan(); 343 return selectAudioCard(mCardsParser.getDefaultCard()); 344 } 345 346 /* package */ void usbDeviceAdded(UsbDevice usbDevice) { 347 if (DEBUG) { 348 Slog.d(TAG, "deviceAdded(): " + usbDevice.getManufacturerName() + 349 "nm:" + usbDevice.getProductName()); 350 } 351 352 // Is there an audio interface in there? 353 boolean isAudioDevice = false; 354 355 // FIXME - handle multiple configurations? 356 int interfaceCount = usbDevice.getInterfaceCount(); 357 for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < interfaceCount; 358 ntrfaceIndex++) { 359 UsbInterface ntrface = usbDevice.getInterface(ntrfaceIndex); 360 if (ntrface.getInterfaceClass() == UsbConstants.USB_CLASS_AUDIO) { 361 isAudioDevice = true; 362 } 363 } 364 if (!isAudioDevice) { 365 return; 366 } 367 368 ArrayList<AlsaCardsParser.AlsaCardRecord> prevScanRecs = mCardsParser.getScanRecords(); 369 mCardsParser.scan(); 370 371 int addedCard = -1; 372 ArrayList<AlsaCardsParser.AlsaCardRecord> 373 newScanRecs = mCardsParser.getNewCardRecords(prevScanRecs); 374 if (newScanRecs.size() > 0) { 375 // This is where we select the just connected device 376 // NOTE - to switch to prefering the first-connected device, just always 377 // take the else clause below. 378 addedCard = newScanRecs.get(0).mCardNum; 379 } else { 380 addedCard = mCardsParser.getDefaultUsbCard(); 381 } 382 383 // If the default isn't a USB device, let the existing "select internal mechanism" 384 // handle the selection. 385 if (mCardsParser.isCardUsb(addedCard)) { 386 UsbAudioDevice audioDevice = selectAudioCard(addedCard); 387 if (audioDevice != null) { 388 mAudioDevices.put(usbDevice, 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 434 /* package */ void usbDeviceRemoved(UsbDevice usbDevice) { 435 if (DEBUG) { 436 Slog.d(TAG, "deviceRemoved(): " + usbDevice.getManufacturerName() + 437 " " + usbDevice.getProductName()); 438 } 439 440 UsbAudioDevice audioDevice = mAudioDevices.remove(usbDevice); 441 if (audioDevice != null) { 442 if (audioDevice.mHasPlayback || audioDevice.mHasPlayback) { 443 notifyDeviceState(audioDevice, false); 444 445 // if there any external devices left, select one of them 446 selectDefaultDevice(); 447 } 448 } 449 UsbMidiDevice usbMidiDevice = mMidiDevices.remove(usbDevice); 450 if (usbMidiDevice != null) { 451 IoUtils.closeQuietly(usbMidiDevice); 452 } 453 } 454 455 /* package */ void setAccessoryAudioState(boolean enabled, int card, int device) { 456 if (DEBUG) { 457 Slog.d(TAG, "setAccessoryAudioState " + enabled + " " + card + " " + device); 458 } 459 if (enabled) { 460 mAccessoryAudioDevice = new UsbAudioDevice(card, device, true, false, 461 UsbAudioDevice.kAudioDeviceClass_External); 462 notifyDeviceState(mAccessoryAudioDevice, true); 463 } else if (mAccessoryAudioDevice != null) { 464 notifyDeviceState(mAccessoryAudioDevice, false); 465 mAccessoryAudioDevice = null; 466 } 467 } 468 469 /* package */ void setPeripheralMidiState(boolean enabled, int card, int device) { 470 if (!mHasMidiFeature) { 471 return; 472 } 473 474 if (enabled && mPeripheralMidiDevice == null) { 475 Bundle properties = new Bundle(); 476 Resources r = mContext.getResources(); 477 properties.putString(MidiDeviceInfo.PROPERTY_NAME, r.getString( 478 com.android.internal.R.string.usb_midi_peripheral_name)); 479 properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, r.getString( 480 com.android.internal.R.string.usb_midi_peripheral_manufacturer_name)); 481 properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, r.getString( 482 com.android.internal.R.string.usb_midi_peripheral_product_name)); 483 properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card); 484 properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device); 485 mPeripheralMidiDevice = UsbMidiDevice.create(mContext, properties, card, device); 486 } else if (!enabled && mPeripheralMidiDevice != null) { 487 IoUtils.closeQuietly(mPeripheralMidiDevice); 488 mPeripheralMidiDevice = null; 489 } 490 } 491 492 // 493 // Devices List 494 // 495 public ArrayList<UsbAudioDevice> getConnectedDevices() { 496 ArrayList<UsbAudioDevice> devices = new ArrayList<UsbAudioDevice>(mAudioDevices.size()); 497 for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) { 498 devices.add(entry.getValue()); 499 } 500 return devices; 501 } 502 503 // 504 // Logging 505 // 506 public void dump(IndentingPrintWriter pw) { 507 pw.println("USB Audio Devices:"); 508 for (UsbDevice device : mAudioDevices.keySet()) { 509 pw.println(" " + device.getDeviceName() + ": " + mAudioDevices.get(device)); 510 } 511 pw.println("USB MIDI Devices:"); 512 for (UsbDevice device : mMidiDevices.keySet()) { 513 pw.println(" " + device.getDeviceName() + ": " + mMidiDevices.get(device)); 514 } 515 } 516 517 public void logDevicesList(String title) { 518 if (DEBUG) { 519 for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) { 520 Slog.i(TAG, "UsbDevice-------------------"); 521 Slog.i(TAG, "" + (entry != null ? entry.getKey() : "[none]")); 522 Slog.i(TAG, "UsbAudioDevice--------------"); 523 Slog.i(TAG, "" + entry.getValue()); 524 } 525 } 526 } 527 528 // This logs a more terse (and more readable) version of the devices list 529 public void logDevices(String title) { 530 if (DEBUG) { 531 Slog.i(TAG, title); 532 for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) { 533 Slog.i(TAG, entry.getValue().toShortString()); 534 } 535 } 536 } 537 } 538