diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java index 17c354a8a80e8..e1de58e19fcc5 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java @@ -23,7 +23,9 @@ import android.hardware.usb.UsbDevice; import android.media.IAudioService; import android.media.midi.MidiDeviceInfo; import android.os.Bundle; +import android.os.FileObserver; import android.os.ServiceManager; +import android.os.SystemClock; import android.provider.Settings; import android.service.usb.UsbAlsaManagerProto; import android.util.Slog; @@ -34,9 +36,11 @@ import com.android.server.usb.descriptors.UsbDescriptorParser; import libcore.io.IoUtils; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; /** @@ -52,6 +56,11 @@ public final class UsbAlsaManager { private static final String ALSA_DIRECTORY = "/dev/snd/"; + private static final int ALSA_DEVICE_TYPE_UNKNOWN = 0; + private static final int ALSA_DEVICE_TYPE_PLAYBACK = 1; + private static final int ALSA_DEVICE_TYPE_CAPTURE = 2; + private static final int ALSA_DEVICE_TYPE_MIDI = 3; + private final Context mContext; private IAudioService mAudioService; private final boolean mHasMidiFeature; @@ -116,6 +125,21 @@ public final class UsbAlsaManager { // UsbMidiDevice for USB peripheral mode (gadget) device private UsbMidiDevice mPeripheralMidiDevice = null; + private final HashSet mAlsaCards = new HashSet<>(); + private final FileObserver mAlsaObserver = new FileObserver(new File(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 */ UsbAlsaManager(Context context) { mContext = context; mHasMidiFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI); @@ -124,6 +148,7 @@ public final class UsbAlsaManager { public void systemReady() { mAudioService = IAudioService.Stub.asInterface( ServiceManager.getService(Context.AUDIO_SERVICE)); + mAlsaObserver.startWatching(); } /** @@ -224,6 +249,8 @@ public final class UsbAlsaManager { return; } + waitForAlsaDevice(cardRec.getCardNum(), true /*isAdded*/); + // Add it to the devices list boolean hasInput = parser.hasInput() && !isDeviceDenylisted(usbDevice.getVendorId(), usbDevice.getProductId(), @@ -322,6 +349,7 @@ public final class UsbAlsaManager { UsbAlsaDevice alsaDevice = removeAlsaDeviceFromList(deviceAddress); Slog.i(TAG, "USB Audio Device Removed: " + alsaDevice); if (alsaDevice != null && alsaDevice == mSelectedDevice) { + waitForAlsaDevice(alsaDevice.getCardNum(), false /*isAdded*/); deselectAlsaDevice(); selectDefaultDevice(); // if there any external devices left, select one of them } @@ -361,6 +389,89 @@ public final class UsbAlsaManager { } } + private boolean waitForAlsaDevice(int card, boolean isAdded) { + if (DEBUG) { + Slog.e(TAG, "waitForAlsaDevice(c:" + card + ")"); + } + + // This value was empirically determined. + final int kWaitTimeMs = 2500; + + synchronized (mAlsaCards) { + long timeoutMs = SystemClock.elapsedRealtime() + kWaitTimeMs; + while ((isAdded ^ mAlsaCards.contains(card)) + && timeoutMs > SystemClock.elapsedRealtime()) { + long waitTimeMs = timeoutMs - SystemClock.elapsedRealtime(); + if (waitTimeMs > 0) { + try { + mAlsaCards.wait(waitTimeMs); + } catch (InterruptedException e) { + Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file."); + } + } + } + final boolean cardFound = mAlsaCards.contains(card); + if ((isAdded ^ cardFound) && timeoutMs > SystemClock.elapsedRealtime()) { + Slog.e(TAG, "waitForAlsaDevice(" + card + ") timeout"); + } else { + Slog.i(TAG, "waitForAlsaDevice for device card=" + card + ", isAdded=" + isAdded + + ", found=" + cardFound); + } + return cardFound; + } + } + + private int getCardNumberFromAlsaFilePath(String path) { + int type = ALSA_DEVICE_TYPE_UNKNOWN; + if (path.startsWith("pcmC")) { + if (path.endsWith("p")) { + type = ALSA_DEVICE_TYPE_PLAYBACK; + } else if (path.endsWith("c")) { + type = ALSA_DEVICE_TYPE_CAPTURE; + } + } else if (path.startsWith("midiC")) { + type = ALSA_DEVICE_TYPE_MIDI; + } + + if (type == ALSA_DEVICE_TYPE_UNKNOWN) { + Slog.i(TAG, "Unknown type file(" + path + ") added."); + return -1; + } + try { + int c_index = path.indexOf('C'); + int d_index = path.indexOf('D'); + return Integer.parseInt(path.substring(c_index + 1, d_index)); + } catch (Exception e) { + Slog.e(TAG, "Could not parse ALSA file name " + path, e); + return -1; + } + } + + private void alsaFileAdded(String path) { + Slog.i(TAG, "alsaFileAdded(" + path + ")"); + final int card = getCardNumberFromAlsaFilePath(path); + if (card == -1) { + return; + } + synchronized (mAlsaCards) { + if (!mAlsaCards.contains(card)) { + Slog.d(TAG, "Adding ALSA device card=" + card); + mAlsaCards.add(card); + mAlsaCards.notifyAll(); + } + } + } + + private void alsaFileRemoved(String path) { + final int card = getCardNumberFromAlsaFilePath(path); + if (card == -1) { + return; + } + synchronized (mAlsaCards) { + mAlsaCards.remove(card); + } + } + // // Devices List //