From fb0a22eb62567f39447495622c2198173ca6e287 Mon Sep 17 00:00:00 2001 From: Mike Lockwood Date: Mon, 1 Dec 2014 11:46:40 -0800 Subject: [PATCH] Remove polling for ALSA device files in USB audio device detection. Change-Id: I993fe41707fc02c9449800c7325aaf7270fb892e --- .../android/server/usb/UsbAudioManager.java | 170 +++++++++++++++--- .../android/server/usb/UsbHostManager.java | 2 + 2 files changed, 150 insertions(+), 22 deletions(-) diff --git a/services/usb/java/com/android/server/usb/UsbAudioManager.java b/services/usb/java/com/android/server/usb/UsbAudioManager.java index bb45ee88c0dce..95eb92b07ee86 100644 --- a/services/usb/java/com/android/server/usb/UsbAudioManager.java +++ b/services/usb/java/com/android/server/usb/UsbAudioManager.java @@ -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 mAudioDevices = new HashMap(); + private final HashMap mAlsaDevices + = new HashMap(); + + 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); + } } } diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java index e769bda1eb6f0..2fc8fd3237ea9 100644 --- a/services/usb/java/com/android/server/usb/UsbHostManager.java +++ b/services/usb/java/com/android/server/usb/UsbHostManager.java @@ -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.