Merge "Remove polling for ALSA device files in USB audio device detection."

This commit is contained in:
Mike Lockwood
2014-12-01 20:25:18 +00:00
committed by Android (Google) Code Review
2 changed files with 150 additions and 22 deletions

View File

@@ -24,6 +24,8 @@ import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbInterface;
import android.media.AudioManager;
import android.os.FileObserver;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Slog;
@@ -39,6 +41,8 @@ public class UsbAudioManager {
private static final String TAG = UsbAudioManager.class.getSimpleName();
private static final boolean DEBUG = false;
private static final String ALSA_DIRECTORY = "/dev/snd/";
private final Context mContext;
private final class AudioDevice {
@@ -69,13 +73,74 @@ public class UsbAudioManager {
}
}
private final class AlsaDevice {
public static final int TYPE_UNKNOWN = 0;
public static final int TYPE_PLAYBACK = 1;
public static final int TYPE_CAPTURE = 2;
public static final int TYPE_MIDI = 3;
public int mCard;
public int mDevice;
public int mType;
public AlsaDevice(int type, int card, int device) {
mType = type;
mCard = card;
mDevice = device;
}
public boolean equals(Object obj) {
if (! (obj instanceof AlsaDevice)) {
return false;
}
AlsaDevice other = (AlsaDevice)obj;
return (mType == other.mType && mCard == other.mCard && mDevice == other.mDevice);
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("AlsaDevice: [card: " + mCard);
sb.append(", device: " + mDevice);
sb.append(", type: " + mType);
sb.append("]");
return sb.toString();
}
}
private final HashMap<UsbDevice,AudioDevice> mAudioDevices
= new HashMap<UsbDevice,AudioDevice>();
private final HashMap<String,AlsaDevice> mAlsaDevices
= new HashMap<String,AlsaDevice>();
private final FileObserver mAlsaObserver = new FileObserver(ALSA_DIRECTORY,
FileObserver.CREATE | FileObserver.DELETE) {
public void onEvent(int event, String path) {
switch (event) {
case FileObserver.CREATE:
alsaFileAdded(path);
break;
case FileObserver.DELETE:
alsaFileRemoved(path);
break;
}
}
};
/* package */ UsbAudioManager(Context context) {
mContext = context;
}
public void systemReady() {
mAlsaObserver.startWatching();
// add existing alsa devices
File[] files = new File(ALSA_DIRECTORY).listFiles();
for (int i = 0; i < files.length; i++) {
alsaFileAdded(files[i].getName());
}
}
// Broadcasts the arrival/departure of a USB audio interface
// audioDevice - the AudioDevice that was added or removed
// enabled - if true, we're connecting a device (it's arrived), else disconnecting
@@ -93,30 +158,87 @@ public class UsbAudioManager {
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
private boolean waitForAlsaFile(int card, int device, boolean capture) {
// These values were empirically determined.
final int kNumRetries = 5;
final int kSleepTime = 500; // ms
String alsaDevPath = "/dev/snd/pcmC" + card + "D" + device + (capture ? "c" : "p");
File alsaDevFile = new File(alsaDevPath);
boolean exists = false;
for (int retry = 0; !exists && retry < kNumRetries; retry++) {
exists = alsaDevFile.exists();
if (!exists) {
try {
Thread.sleep(kSleepTime);
} catch (IllegalThreadStateException ex) {
Slog.d(TAG, "usb: IllegalThreadStateException while waiting for ALSA file.");
} catch (java.lang.InterruptedException ex) {
Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file.");
private AlsaDevice waitForAlsaDevice(int card, int device, int type) {
AlsaDevice testDevice = new AlsaDevice(type, card, device);
// This value was empirically determined.
final int kWaitTime = 2500; // ms
synchronized(mAlsaDevices) {
long timeout = SystemClock.elapsedRealtime() + kWaitTime;
do {
if (mAlsaDevices.values().contains(testDevice)) {
return testDevice;
}
long waitTime = timeout - SystemClock.elapsedRealtime();
if (waitTime > 0) {
try {
mAlsaDevices.wait(waitTime);
} catch (InterruptedException e) {
Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file.");
}
}
} while (timeout > SystemClock.elapsedRealtime());
}
Slog.e(TAG, "waitForAlsaDevice failed for " + testDevice);
return null;
}
private void alsaFileAdded(String name) {
int type = AlsaDevice.TYPE_UNKNOWN;
int card = -1, device = -1;
if (name.startsWith("pcmC")) {
if (name.endsWith("p")) {
type = AlsaDevice.TYPE_PLAYBACK;
} else if (name.endsWith("c")) {
type = AlsaDevice.TYPE_CAPTURE;
}
} else if (name.startsWith("midiC")) {
type = AlsaDevice.TYPE_MIDI;
}
if (type != AlsaDevice.TYPE_UNKNOWN) {
try {
int c_index = name.indexOf('C');
int d_index = name.indexOf('D');
int end = name.length();
if (type == AlsaDevice.TYPE_PLAYBACK || type == AlsaDevice.TYPE_CAPTURE) {
// skip trailing 'p' or 'c'
end--;
}
card = Integer.parseInt(name.substring(c_index + 1, d_index));
device = Integer.parseInt(name.substring(d_index + 1, end));
} catch (Exception e) {
Slog.e(TAG, "Could not parse ALSA file name " + name, e);
return;
}
synchronized(mAlsaDevices) {
if (mAlsaDevices.get(name) == null) {
AlsaDevice alsaDevice = new AlsaDevice(type, card, device);
Slog.d(TAG, "Adding ALSA device " + alsaDevice);
mAlsaDevices.put(name, alsaDevice);
mAlsaDevices.notifyAll();
}
}
}
}
return exists;
private void alsaFileRemoved(String path) {
synchronized(mAlsaDevices) {
AlsaDevice device = mAlsaDevices.remove(path);
if (device != null) {
Slog.d(TAG, "ALSA device removed: " + device);
}
}
}
/* package */ void deviceAdded(UsbDevice usbDevice) {
if (DEBUG) {
Slog.d(TAG, "deviceAdded(): " + usbDevice);
}
// Is there an audio interface in there?
boolean isAudioDevice = false;
@@ -134,7 +256,7 @@ public class UsbAudioManager {
}
//TODO(pmclean) The "Parser" objects inspect files in "/proc/asound" which we presume is
// present, unlike the waitForAlsaFile() which waits on a file in /dev/snd. It is not
// present, unlike the waitForAlsaDevice() which waits on a file in /dev/snd. It is not
// clear why this works, or that it can be relied on going forward. Needs further
// research.
AlsaCardsParser cardsParser = new AlsaCardsParser();
@@ -157,19 +279,21 @@ public class UsbAudioManager {
// Playback device file needed/present?
if (hasPlayback &&
!waitForAlsaFile(card, device, false)) {
waitForAlsaDevice(card, device, AlsaDevice.TYPE_PLAYBACK) == null) {
return;
}
// Capture device file needed/present?
if (hasCapture &&
!waitForAlsaFile(card, device, true)) {
waitForAlsaDevice(card, device, AlsaDevice.TYPE_CAPTURE) == null) {
return;
}
if (DEBUG) {
Slog.d(TAG,
"usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture);
"usb: hasPlayback:" + hasPlayback +
" hasCapture:" + hasCapture +
" hasMidi:" + hasMidi);
}
AudioDevice audioDevice = new AudioDevice(card, device, hasPlayback, hasCapture, hasMidi);
@@ -184,7 +308,9 @@ public class UsbAudioManager {
AudioDevice audioDevice = mAudioDevices.remove(device);
if (audioDevice != null) {
sendDeviceNotification(audioDevice, false);
if (audioDevice.mHasPlayback || audioDevice.mHasPlayback) {
sendDeviceNotification(audioDevice, false);
}
}
}

View File

@@ -245,6 +245,8 @@ public class UsbHostManager {
}
public void systemReady() {
mUsbAudioManager.systemReady();
synchronized (mLock) {
// Create a thread to call into native code to wait for USB host events.
// This thread will call us back on usbDeviceAdded and usbDeviceRemoved.