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

Commit fd7d3108 authored by Paul McLean's avatar Paul McLean Committed by Android (Google) Code Review
Browse files

Merge "Implement USB Audio across Nexus Devices Fix issues with connecting non-audio USB devices."

parents d1d7c491 df361469
Loading
Loading
Loading
Loading
+35 −0
Original line number Diff line number Diff line
@@ -205,14 +205,49 @@ public class AlsaDevicesParser {
        return mHasPlaybackDevices;
    }

    public boolean hasPlaybackDevices(int card) {
        for (int index = 0; index < deviceRecords_.size(); index++) {
            AlsaDeviceRecord deviceRecord = deviceRecords_.get(index);
            if (deviceRecord.mCardNum == card &&
                deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio &&
                deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Playback) {
                return true;
            }
        }
        return false;
    }

    public boolean hasCaptureDevices() {
        return mHasCaptureDevices;
    }

    public boolean hasCaptureDevices(int card) {
        for (int index = 0; index < deviceRecords_.size(); index++) {
            AlsaDeviceRecord deviceRecord = deviceRecords_.get(index);
            if (deviceRecord.mCardNum == card &&
                deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio &&
                deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Capture) {
                return true;
            }
        }
        return false;
    }

    public boolean hasMIDIDevices() {
        return mHasMIDIDevices;
    }

    public boolean hasMIDIDevices(int card) {
        for (int index = 0; index < deviceRecords_.size(); index++) {
            AlsaDeviceRecord deviceRecord = deviceRecords_.get(index);
            if (deviceRecord.mCardNum == card &&
                deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_MIDI) {
                return true;
            }
        }
        return false;
    }

    public void scan() {
        deviceRecords_.clear();

+22 −14
Original line number Diff line number Diff line
@@ -4083,8 +4083,19 @@ public class AudioService extends IAudioService.Stub {
                        }
                    }
                }
            } else if (action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG) ||
                           action.equals(Intent.ACTION_USB_AUDIO_DEVICE_PLUG)) {
            } else if (action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG)) {
                state = intent.getIntExtra("state", 0);

                int alsaCard = intent.getIntExtra("card", -1);
                int alsaDevice = intent.getIntExtra("device", -1);

                String params = (alsaCard == -1 && alsaDevice == -1 ? ""
                                    : "card=" + alsaCard + ";device=" + alsaDevice);

                // Playback Device
                device = AudioSystem.DEVICE_OUT_USB_ACCESSORY;
                setWiredDeviceConnectionState(device, state, params);
            } else if (action.equals(Intent.ACTION_USB_AUDIO_DEVICE_PLUG)) {
                state = intent.getIntExtra("state", 0);

                int alsaCard = intent.getIntExtra("card", -1);
@@ -4096,21 +4107,18 @@ public class AudioService extends IAudioService.Stub {
                String params = (alsaCard == -1 && alsaDevice == -1 ? ""
                                    : "card=" + alsaCard + ";device=" + alsaDevice);

                //TODO(pmclean) - Ignore for now the hasPlayback & hasCapture flags since we
                // still need to call setWiredDeviceConnectionState() on disconnect (when we
                // don't have card/device files to parse for this info). We will need to store
                // that info here when we get smarter about multiple USB card/devices.
                // Playback Device
                device = action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG) ?
                        AudioSystem.DEVICE_OUT_USB_ACCESSORY : AudioSystem.DEVICE_OUT_USB_DEVICE;
                if (hasPlayback) {
                    device = AudioSystem.DEVICE_OUT_USB_DEVICE;
                    setWiredDeviceConnectionState(device, state, params);
                }

                // Capture Device
                device = action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG) ?
                    AudioSystem.DEVICE_IN_USB_ACCESSORY : AudioSystem.DEVICE_IN_USB_DEVICE;
                if (hasCapture) {
                    device = AudioSystem.DEVICE_IN_USB_DEVICE;
                    setWiredDeviceConnectionState(device, state, params);
                }
            else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
            } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
                boolean broadcast = false;
                int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
                synchronized (mScoClients) {
+128 −69
Original line number Diff line number Diff line
@@ -29,6 +29,8 @@ import android.os.Parcelable;
import android.os.UserHandle;
import android.util.Slog;

import com.android.alsascan.AlsaCardsParser;
import com.android.alsascan.AlsaDevicesParser;
import com.android.internal.annotations.GuardedBy;

import java.io.File;
@@ -37,7 +39,6 @@ import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;

/**
 * UsbHostManager manages USB state in host mode.
@@ -62,6 +63,15 @@ public class UsbHostManager {
    private ArrayList<UsbInterface> mNewInterfaces;
    private ArrayList<UsbEndpoint> mNewEndpoints;

    // Attributes of any connected USB audio device.
    //TODO(pmclean) When we extend to multiple, USB Audio devices, we will need to get
    // more clever about this.
    private int mConnectedUsbCard = -1;
    private int mConnectedUsbDeviceNum = -1;
    private boolean mConnectedHasPlayback = false;
    private boolean mConnectedHasCapture = false;
    private boolean mConnectedHasMIDI = false;

    @GuardedBy("mLock")
    private UsbSettingsManager mCurrentSettings;

@@ -126,9 +136,27 @@ public class UsbHostManager {
        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
    }

    static boolean isBuiltInUsbDevice(String deviceName) {
      // This may be too broad an assumption
      return deviceName.equals("/dev/bus/usb/001/001");
    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.");
                }
            }
        }

        return exists;
    }

    /* Called from JNI in monitorUsbHostBus() to report new USB devices
@@ -151,47 +179,13 @@ public class UsbHostManager {
            // midi streaming: 0x03

            // some useful debugging info
          Slog.d(TAG, "usb:UsbHostManager.usbDeviceAdded()");
          Slog.d(TAG, "usb: nm:" + deviceName +
              " vnd:" + vendorID +
              " prd:" + productID +
              " cls:" + deviceClass +
              " sub:" + deviceSubclass +
              " proto:" + deviceProtocol);
      }

      if (!isBuiltInUsbDevice(deviceName)) {
          //TODO(pmclean) we will need this when we need to support USB interfaces
          // beyond card1, device0 but turn them off for now
          //com.android.alsascan.AlsaCardsParser cardsParser =
          //    new com.android.alsascan.AlsaCardsParser();
          //cardsParser.scan();
          //cardsParser.Log();

          // But we need to parse the device to determine its capabilities.
          com.android.alsascan.AlsaDevicesParser devicesParser =
              new com.android.alsascan.AlsaDevicesParser();
          devicesParser.scan();
          //devicesParser.Log();

          boolean hasPlaybackDevices = devicesParser.hasPlaybackDevices();
          boolean hasCaptureDevices = devicesParser.hasCaptureDevices();
          boolean hasMIDI = devicesParser.hasMIDIDevices();

          if (DEBUG_AUDIO) {
              Slog.d(TAG, "usb: hasPlayback:" + hasPlaybackDevices
                      + " hasCapture:" + hasCaptureDevices);
            Slog.d(TAG, "usb: nm:" + deviceName + " vnd:" + vendorID + " prd:" + productID + " cls:"
                    + deviceClass + " sub:" + deviceSubclass + " proto:" + deviceProtocol);
        }

          //TODO(pmclean)
          // For now just assume that any USB device that is attached is:
          // 1. An audio interface and
          // 2. is card:1 device:0
          int cardNum = 1;
          int deviceNum = 0;
          sendDeviceNotification(cardNum, deviceNum, true,
                                 hasPlaybackDevices, hasCaptureDevices, hasMIDI);
        }
        // OK this is non-obvious, but true. One can't tell if the device being attached is even
        // potentially an audio device without parsing the interface descriptors, so punt on any
        // such test until endUsbDeviceAdded() when we have that info.

        if (isBlackListed(deviceName) ||
                isBlackListed(deviceClass, deviceSubclass, deviceProtocol)) {
@@ -217,6 +211,7 @@ public class UsbHostManager {
            mNewInterfaces = new ArrayList<UsbInterface>();
            mNewEndpoints = new ArrayList<UsbEndpoint>();
        }

        return true;
    }

@@ -270,6 +265,17 @@ public class UsbHostManager {
                    mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
        }

        // Is there an audio interface in there?
        final int kUsbClassId_Audio = 0x01;
        boolean isAudioDevice = false;
        for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < mNewInterfaces.size();
                ntrfaceIndex++) {
            UsbInterface ntrface = mNewInterfaces.get(ntrfaceIndex);
            if (ntrface.getInterfaceClass() == kUsbClassId_Audio) {
                isAudioDevice = true;
            }
        }

        synchronized (mLock) {
            if (mNewDevice != null) {
                mNewDevice.setConfigurations(
@@ -285,6 +291,48 @@ public class UsbHostManager {
            mNewInterfaces = null;
            mNewEndpoints = null;
        }

        if (!isAudioDevice) {
            return; // bail
        }

        //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
        // clear why this works, or that it can be relied on going forward.  Needs further
        // research.
        AlsaCardsParser cardsParser = new AlsaCardsParser();
        cardsParser.scan();
        // cardsParser.Log();

        // But we need to parse the device to determine its capabilities.
        AlsaDevicesParser devicesParser = new AlsaDevicesParser();
        devicesParser.scan();
        // devicesParser.Log();

        // The protocol for now will be to select the last-connected (highest-numbered)
        // Alsa Card.
        mConnectedUsbCard = cardsParser.getNumCardRecords() - 1;
        mConnectedUsbDeviceNum = 0;

        if (!waitForAlsaFile(mConnectedUsbCard, mConnectedUsbDeviceNum, false)) {
            return;
        }

        mConnectedHasPlayback = devicesParser.hasPlaybackDevices(mConnectedUsbCard);
        mConnectedHasCapture = devicesParser.hasCaptureDevices(mConnectedUsbCard);
        mConnectedHasMIDI = devicesParser.hasMIDIDevices(mConnectedUsbCard);

        if (DEBUG_AUDIO) {
            Slog.d(TAG,
                    "usb: hasPlayback:" + mConnectedHasPlayback + " hasCapture:" + mConnectedHasCapture);
        }

        sendDeviceNotification(mConnectedUsbCard,
                mConnectedUsbDeviceNum,
                true,
                mConnectedHasPlayback,
                mConnectedHasCapture,
                mConnectedHasMIDI);
    }

    /* Called from JNI in monitorUsbHostBus to report USB device removal */
@@ -293,8 +341,19 @@ public class UsbHostManager {
          Slog.d(TAG, "usb:UsbHostManager.usbDeviceRemoved() nm:" + deviceName);
        }

        // Same assumptions as the fake-out above
        sendDeviceNotification(1, 0, false, /*NA*/false, /*NA*/false, /*NA*/false);
        if (mConnectedUsbCard != -1 && mConnectedUsbDeviceNum != -1) {
            sendDeviceNotification(mConnectedUsbCard,
                    mConnectedUsbDeviceNum,
                    false,
                    mConnectedHasPlayback,
                    mConnectedHasCapture,
                    mConnectedHasMIDI);
            mConnectedUsbCard = -1;
            mConnectedUsbDeviceNum = -1;
            mConnectedHasPlayback = false;
            mConnectedHasCapture = false;
            mConnectedHasMIDI = false;
        }

        synchronized (mLock) {
            UsbDevice device = mDevices.remove(deviceName);