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.alsa.AlsaCardsParser; 20 import android.alsa.AlsaDevicesParser; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.hardware.usb.UsbConstants; 24 import android.hardware.usb.UsbDevice; 25 import android.hardware.usb.UsbInterface; 26 import android.media.AudioManager; 27 import android.os.UserHandle; 28 import android.util.Slog; 29 30 import java.io.File; 31 import java.io.FileDescriptor; 32 import java.io.PrintWriter; 33 import java.util.HashMap; 34 35 /** 36 * UsbAudioManager manages USB audio devices. 37 */ 38 public class UsbAudioManager { 39 private static final String TAG = UsbAudioManager.class.getSimpleName(); 40 private static final boolean DEBUG = false; 41 42 private final Context mContext; 43 44 private final class AudioDevice { 45 public int mCard; 46 public int mDevice; 47 public boolean mHasPlayback; 48 public boolean mHasCapture; 49 public boolean mHasMIDI; 50 AudioDevice(int card, int device, boolean hasPlayback, boolean hasCapture, boolean hasMidi)51 public AudioDevice(int card, int device, 52 boolean hasPlayback, boolean hasCapture, boolean hasMidi) { 53 mCard = card; 54 mDevice = device; 55 mHasPlayback = hasPlayback; 56 mHasCapture = hasCapture; 57 mHasMIDI = hasMidi; 58 } 59 toString()60 public String toString() { 61 StringBuilder sb = new StringBuilder(); 62 sb.append("AudioDevice: [card: " + mCard); 63 sb.append(", device: " + mDevice); 64 sb.append(", hasPlayback: " + mHasPlayback); 65 sb.append(", hasCapture: " + mHasCapture); 66 sb.append(", hasMidi: " + mHasMIDI); 67 sb.append("]"); 68 return sb.toString(); 69 } 70 } 71 72 private final HashMap<UsbDevice,AudioDevice> mAudioDevices 73 = new HashMap<UsbDevice,AudioDevice>(); 74 UsbAudioManager(Context context)75 /* package */ UsbAudioManager(Context context) { 76 mContext = context; 77 } 78 79 // Broadcasts the arrival/departure of a USB audio interface 80 // audioDevice - the AudioDevice that was added or removed 81 // enabled - if true, we're connecting a device (it's arrived), else disconnecting sendDeviceNotification(AudioDevice audioDevice, boolean enabled)82 private void sendDeviceNotification(AudioDevice audioDevice, boolean enabled) { 83 // send a sticky broadcast containing current USB state 84 Intent intent = new Intent(AudioManager.ACTION_USB_AUDIO_DEVICE_PLUG); 85 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 86 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 87 intent.putExtra("state", enabled ? 1 : 0); 88 intent.putExtra("card", audioDevice.mCard); 89 intent.putExtra("device", audioDevice.mDevice); 90 intent.putExtra("hasPlayback", audioDevice.mHasPlayback); 91 intent.putExtra("hasCapture", audioDevice.mHasCapture); 92 intent.putExtra("hasMIDI", audioDevice.mHasMIDI); 93 mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); 94 } 95 waitForAlsaFile(int card, int device, boolean capture)96 private boolean waitForAlsaFile(int card, int device, boolean capture) { 97 // These values were empirically determined. 98 final int kNumRetries = 5; 99 final int kSleepTime = 500; // ms 100 String alsaDevPath = "/dev/snd/pcmC" + card + "D" + device + (capture ? "c" : "p"); 101 File alsaDevFile = new File(alsaDevPath); 102 boolean exists = false; 103 for (int retry = 0; !exists && retry < kNumRetries; retry++) { 104 exists = alsaDevFile.exists(); 105 if (!exists) { 106 try { 107 Thread.sleep(kSleepTime); 108 } catch (IllegalThreadStateException ex) { 109 Slog.d(TAG, "usb: IllegalThreadStateException while waiting for ALSA file."); 110 } catch (java.lang.InterruptedException ex) { 111 Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file."); 112 } 113 } 114 } 115 116 return exists; 117 } 118 deviceAdded(UsbDevice usbDevice)119 /* package */ void deviceAdded(UsbDevice usbDevice) { 120 // Is there an audio interface in there? 121 boolean isAudioDevice = false; 122 123 // FIXME - handle multiple configurations? 124 int interfaceCount = usbDevice.getInterfaceCount(); 125 for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < interfaceCount; 126 ntrfaceIndex++) { 127 UsbInterface ntrface = usbDevice.getInterface(ntrfaceIndex); 128 if (ntrface.getInterfaceClass() == UsbConstants.USB_CLASS_AUDIO) { 129 isAudioDevice = true; 130 } 131 } 132 if (!isAudioDevice) { 133 return; 134 } 135 136 //TODO(pmclean) The "Parser" objects inspect files in "/proc/asound" which we presume is 137 // present, unlike the waitForAlsaFile() which waits on a file in /dev/snd. It is not 138 // clear why this works, or that it can be relied on going forward. Needs further 139 // research. 140 AlsaCardsParser cardsParser = new AlsaCardsParser(); 141 cardsParser.scan(); 142 // cardsParser.Log(); 143 144 // But we need to parse the device to determine its capabilities. 145 AlsaDevicesParser devicesParser = new AlsaDevicesParser(); 146 devicesParser.scan(); 147 // devicesParser.Log(); 148 149 // The protocol for now will be to select the last-connected (highest-numbered) 150 // Alsa Card. 151 int card = cardsParser.getNumCardRecords() - 1; 152 int device = 0; 153 154 boolean hasPlayback = devicesParser.hasPlaybackDevices(card); 155 boolean hasCapture = devicesParser.hasCaptureDevices(card); 156 boolean hasMidi = devicesParser.hasMIDIDevices(card); 157 158 // Playback device file needed/present? 159 if (hasPlayback && 160 !waitForAlsaFile(card, device, false)) { 161 return; 162 } 163 164 // Capture device file needed/present? 165 if (hasCapture && 166 !waitForAlsaFile(card, device, true)) { 167 return; 168 } 169 170 if (DEBUG) { 171 Slog.d(TAG, 172 "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture); 173 } 174 175 AudioDevice audioDevice = new AudioDevice(card, device, hasPlayback, hasCapture, hasMidi); 176 mAudioDevices.put(usbDevice, audioDevice); 177 sendDeviceNotification(audioDevice, true); 178 } 179 deviceRemoved(UsbDevice device)180 /* package */ void deviceRemoved(UsbDevice device) { 181 if (DEBUG) { 182 Slog.d(TAG, "deviceRemoved(): " + device); 183 } 184 185 AudioDevice audioDevice = mAudioDevices.remove(device); 186 if (audioDevice != null) { 187 sendDeviceNotification(audioDevice, false); 188 } 189 } 190 dump(FileDescriptor fd, PrintWriter pw)191 public void dump(FileDescriptor fd, PrintWriter pw) { 192 pw.println(" USB AudioDevices:"); 193 for (UsbDevice device : mAudioDevices.keySet()) { 194 pw.println(" " + device.getDeviceName() + ": " + mAudioDevices.get(device)); 195 } 196 } 197 } 198