Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e2707bf8 authored by jiabin's avatar jiabin
Browse files

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
parent 812d9a94
Loading
Loading
Loading
Loading
+111 −0
Original line number Diff line number Diff line
@@ -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<Integer> 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
    //