From e2707bf84844c216b1c53c6d4779ac80d1e0a673 Mon Sep 17 00:00:00 2001 From: jiabin Date: Tue, 17 Jan 2023 18:05:25 +0000 Subject: [PATCH] Monitor alsa device files to notify USB devices connection. Only notify new USB device connection when there is any alsa device file present. When the USB bus notifies new USB device connection, the alsa device files may not be present. If new USB device connection is notified immediately when the bus detects new device attached, the HAL may not be able to open any input/output stream over USB devices given the alsa device files may not be ready. In this CL, adding the monitor to watch new alsa device files creation and only notify audio framework about new USB device connection when there is nay alsa device file ready. Bug: 261538121 Test: play and record audio via USB headset Change-Id: I2a93f4868f9db82c69dd839cef550244c7d9d3e7 --- .../android/server/usb/UsbAlsaManager.java | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) 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 //