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

Commit 43344259 authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Multiple USB support." into udc-dev am: 739b95b7

parents 130be2b5 739b95b7
Loading
Loading
Loading
Loading
+171 −85
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ import android.util.Slog;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.server.audio.AudioService;

import java.util.Arrays;

/**
 * Represents the ALSA specification, and attributes of an ALSA device.
 */
@@ -36,17 +38,21 @@ public final class UsbAlsaDevice {

    private final int mCardNum;
    private final int mDeviceNum;
    private final String mAlsaCardDeviceString;
    private final String mDeviceAddress;
    private final boolean mHasOutput;
    private final boolean mHasInput;

    private final boolean mIsInputHeadset;
    private final boolean mIsOutputHeadset;
    private final boolean mIsDock;
    // The following two constant will be used as index to access arrays.
    private static final int INPUT = 0;
    private static final int OUTPUT = 1;
    private static final int NUM_DIRECTIONS = 2;
    private static final String[] DIRECTION_STR = {"INPUT", "OUTPUT"};
    private final boolean[] mHasDevice = new boolean[NUM_DIRECTIONS];

    private boolean mSelected = false;
    private int mOutputState;
    private int mInputState;
    private final boolean[] mIsHeadset = new boolean[NUM_DIRECTIONS];
    private final boolean mIsDock;
    private final int[] mDeviceType = new int[NUM_DIRECTIONS];
    private boolean[] mIsSelected = new boolean[NUM_DIRECTIONS];
    private int[] mState = new int[NUM_DIRECTIONS];
    private UsbAlsaJackDetector mJackDetector;
    private IAudioService mAudioService;

@@ -60,11 +66,13 @@ public final class UsbAlsaDevice {
        mCardNum = card;
        mDeviceNum = device;
        mDeviceAddress = deviceAddress;
        mHasOutput = hasOutput;
        mHasInput = hasInput;
        mIsInputHeadset = isInputHeadset;
        mIsOutputHeadset = isOutputHeadset;
        mHasDevice[OUTPUT] = hasOutput;
        mHasDevice[INPUT] = hasInput;
        mIsHeadset[INPUT] = isInputHeadset;
        mIsHeadset[OUTPUT] = isOutputHeadset;
        mIsDock = isDock;
        initDeviceType();
        mAlsaCardDeviceString = getAlsaCardDeviceString();
    }

    /**
@@ -104,28 +112,28 @@ public final class UsbAlsaDevice {
     * @return true if the device supports output.
     */
    public boolean hasOutput() {
        return mHasOutput;
        return mHasDevice[OUTPUT];
    }

    /**
     * @return true if the device supports input (recording).
     */
    public boolean hasInput() {
        return mHasInput;
        return mHasDevice[INPUT];
    }

    /**
     * @return true if the device is a headset for purposes of input.
     * @return true if the device is a headset for purposes of output.
     */
    public boolean isInputHeadset() {
        return mIsInputHeadset;
    public boolean isOutputHeadset() {
        return mIsHeadset[OUTPUT];
    }

    /**
     * @return true if the device is a headset for purposes of output.
     * @return true if the device is a headset for purposes of input.
     */
    public boolean isOutputHeadset() {
        return mIsOutputHeadset;
    public boolean isInputHeadset() {
        return mIsHeadset[INPUT];
    }

    /**
@@ -157,6 +165,9 @@ public final class UsbAlsaDevice {

    /** Begins a jack-detection thread. */
    private synchronized void startJackDetect() {
        if (mJackDetector != null) {
            return;
        }
        // If no jack detect capabilities exist, mJackDetector will be null.
        mJackDetector = UsbAlsaJackDetector.startJackDetect(this);
    }
@@ -171,75 +182,152 @@ public final class UsbAlsaDevice {

    /** Start using this device as the selected USB Audio Device. */
    public synchronized void start() {
        mSelected = true;
        mInputState = 0;
        mOutputState = 0;
        startInput();
        startOutput();
    }

    /** Start using this device as the selected USB input device. */
    public synchronized void startInput() {
        startDevice(INPUT);
    }

    /** Start using this device as selected USB output device. */
    public synchronized void startOutput() {
        startDevice(OUTPUT);
    }

    private void startDevice(int direction) {
        if (mIsSelected[direction]) {
            return;
        }
        mIsSelected[direction] = true;
        mState[direction] = 0;
        startJackDetect();
        updateWiredDeviceConnectionState(true);
        updateWiredDeviceConnectionState(direction, true /*enable*/);
    }

    /** Stop using this device as the selected USB Audio Device. */
    public synchronized void stop() {
        stopJackDetect();
        updateWiredDeviceConnectionState(false);
        mSelected = false;
        stopInput();
        stopOutput();
    }

    /** Updates AudioService with the connection state of the alsaDevice.
     *  Checks ALSA Jack state for inputs and outputs before reporting.
     */
    public synchronized void updateWiredDeviceConnectionState(boolean enable) {
        if (!mSelected) {
            Slog.e(TAG, "updateWiredDeviceConnectionState on unselected AlsaDevice!");
    /** Stop using this device as the selected USB input device. */
    public synchronized void stopInput() {
        if (!mIsSelected[INPUT]) {
            return;
        }
        String alsaCardDeviceString = getAlsaCardDeviceString();
        if (alsaCardDeviceString == null) {
        if (!mIsSelected[OUTPUT]) {
            // Stop jack detection when both input and output are stopped
            stopJackDetect();
        }
        updateInputWiredDeviceConnectionState(false /*enable*/);
        mIsSelected[INPUT] = false;
    }

    /** Stop using this device as the selected USB output device. */
    public synchronized void stopOutput() {
        if (!mIsSelected[OUTPUT]) {
            return;
        }
        try {
            // Output Device
            if (mHasOutput) {
                int device = mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
                        : (mIsOutputHeadset
                            ? AudioSystem.DEVICE_OUT_USB_HEADSET
                            : AudioSystem.DEVICE_OUT_USB_DEVICE);
                if (DEBUG) {
                    Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(device)
                            + " addr:" + alsaCardDeviceString
                            + " name:" + mDeviceName);
        if (!mIsSelected[INPUT]) {
            // Stop jack detection when both input and output are stopped
            stopJackDetect();
        }
                boolean connected = isOutputJackConnected();
                Slog.i(TAG, "OUTPUT JACK connected: " + connected);
                int outputState = (enable && connected) ? 1 : 0;
                if (outputState != mOutputState) {
                    mOutputState = outputState;
                    AudioDeviceAttributes attributes = new AudioDeviceAttributes(device,
                            alsaCardDeviceString, mDeviceName);
                    mAudioService.setWiredDeviceConnectionState(attributes, outputState, TAG);
        updateOutputWiredDeviceConnectionState(false /*enable*/);
        mIsSelected[OUTPUT] = false;
    }

    private void initDeviceType() {
        mDeviceType[INPUT] = mHasDevice[INPUT]
                ? (mIsHeadset[INPUT] ? AudioSystem.DEVICE_IN_USB_HEADSET
                                     : AudioSystem.DEVICE_IN_USB_DEVICE)
                : AudioSystem.DEVICE_NONE;
        mDeviceType[OUTPUT] = mHasDevice[OUTPUT]
                ? (mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
                           : (mIsHeadset[OUTPUT] ? AudioSystem.DEVICE_OUT_USB_HEADSET
                                                 : AudioSystem.DEVICE_OUT_USB_DEVICE))
                : AudioSystem.DEVICE_NONE;
    }

    /**
     * @return the output device type that will be used to notify AudioService about device
     *         connection. If there is no output on this device, {@link AudioSystem#DEVICE_NONE}
     *         will be returned.
     */
    public int getOutputDeviceType() {
        return mDeviceType[OUTPUT];
    }

            // Input Device
            if (mHasInput) {
                int device = mIsInputHeadset
                        ? AudioSystem.DEVICE_IN_USB_HEADSET
                        : AudioSystem.DEVICE_IN_USB_DEVICE;
                boolean connected = isInputJackConnected();
                Slog.i(TAG, "INPUT JACK connected: " + connected);
                int inputState = (enable && connected) ? 1 : 0;
                if (inputState != mInputState) {
                    mInputState = inputState;
                    AudioDeviceAttributes attributes = new AudioDeviceAttributes(device,
                            alsaCardDeviceString, mDeviceName);
                    mAudioService.setWiredDeviceConnectionState(attributes, inputState, TAG);
    /**
     * @return the input device type that will be used to notify AudioService about device
     *         connection. If there is no input on this device, {@link AudioSystem#DEVICE_NONE}
     *         will be returned.
     */
    public int getInputDeviceType() {
        return mDeviceType[INPUT];
    }

    private boolean updateWiredDeviceConnectionState(int direction, boolean enable) {
        if (!mIsSelected[direction]) {
            Slog.e(TAG, "Updating wired device connection state on unselected device");
            return false;
        }
        if (mDeviceType[direction] == AudioSystem.DEVICE_NONE) {
            Slog.d(TAG,
                    "Unable to set device connection state as " + DIRECTION_STR[direction]
                    + " device type is none");
            return false;
        }
        if (mAlsaCardDeviceString == null) {
            Slog.w(TAG, "Failed to update " + DIRECTION_STR[direction] + " device connection "
                    + "state failed as alsa card device string is null");
            return false;
        }
        if (DEBUG) {
            Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(mDeviceType[direction])
                    + " addr:" + mAlsaCardDeviceString
                    + " name:" + mDeviceName);
        }
        boolean connected = direction == INPUT ? isInputJackConnected() : isOutputJackConnected();
        Slog.i(TAG, DIRECTION_STR[direction] + " JACK connected: " + connected);
        int state = (enable && connected) ? 1 : 0;
        if (state != mState[direction]) {
            mState[direction] = state;
            AudioDeviceAttributes attributes = new AudioDeviceAttributes(
                    mDeviceType[direction], mAlsaCardDeviceString, mDeviceName);
            try {
                mAudioService.setWiredDeviceConnectionState(attributes, state, TAG);
            } catch (RemoteException e) {
            Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState");
                Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState for "
                        + DIRECTION_STR[direction]);
                return false;
            }
        }
        return true;
    }

    /**
     * Notify AudioService about the input device connection state.
     *
     * @param enable true to notify the device as connected.
     * @return true only when it successfully notifies AudioService about the device
     *         connection state.
     */
    public synchronized boolean updateInputWiredDeviceConnectionState(boolean enable) {
        return updateWiredDeviceConnectionState(INPUT, enable);
    }

    /**
     * Notify AudioService about the output device connection state.
     *
     * @param enable true to notify the device as connected.
     * @return true only when it successfully notifies AudioService about the device
     *         connection state.
     */
    public synchronized boolean updateOutputWiredDeviceConnectionState(boolean enable) {
        return updateWiredDeviceConnectionState(OUTPUT, enable);
    }

    /**
     * @Override
@@ -249,8 +337,8 @@ public final class UsbAlsaDevice {
        return "UsbAlsaDevice: [card: " + mCardNum
            + ", device: " + mDeviceNum
            + ", name: " + mDeviceName
            + ", hasOutput: " + mHasOutput
            + ", hasInput: " + mHasInput + "]";
            + ", hasOutput: " + mHasDevice[OUTPUT]
            + ", hasInput: " + mHasDevice[INPUT] + "]";
    }

    /**
@@ -262,8 +350,8 @@ public final class UsbAlsaDevice {
        dump.write("card", UsbAlsaDeviceProto.CARD, mCardNum);
        dump.write("device", UsbAlsaDeviceProto.DEVICE, mDeviceNum);
        dump.write("name", UsbAlsaDeviceProto.NAME, mDeviceName);
        dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasOutput);
        dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasInput);
        dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasDevice[OUTPUT]);
        dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasDevice[INPUT]);
        dump.write("address", UsbAlsaDeviceProto.ADDRESS, mDeviceAddress);

        dump.end(token);
@@ -294,10 +382,8 @@ public final class UsbAlsaDevice {
        UsbAlsaDevice other = (UsbAlsaDevice) obj;
        return (mCardNum == other.mCardNum
                && mDeviceNum == other.mDeviceNum
                && mHasOutput == other.mHasOutput
                && mHasInput == other.mHasInput
                && mIsInputHeadset == other.mIsInputHeadset
                && mIsOutputHeadset == other.mIsOutputHeadset
                && Arrays.equals(mHasDevice, other.mHasDevice)
                && Arrays.equals(mIsHeadset, other.mIsHeadset)
                && mIsDock == other.mIsDock);
    }

@@ -310,10 +396,10 @@ public final class UsbAlsaDevice {
        int result = 1;
        result = prime * result + mCardNum;
        result = prime * result + mDeviceNum;
        result = prime * result + (mHasOutput ? 0 : 1);
        result = prime * result + (mHasInput ? 0 : 1);
        result = prime * result + (mIsInputHeadset ? 0 : 1);
        result = prime * result + (mIsOutputHeadset ? 0 : 1);
        result = prime * result + (mHasDevice[OUTPUT] ? 0 : 1);
        result = prime * result + (mHasDevice[INPUT] ? 0 : 1);
        result = prime * result + (mIsHeadset[INPUT] ? 0 : 1);
        result = prime * result + (mIsHeadset[OUTPUT] ? 0 : 1);
        result = prime * result + (mIsDock ? 0 : 1);

        return result;
+2 −1
Original line number Diff line number Diff line
@@ -81,7 +81,8 @@ public final class UsbAlsaJackDetector implements Runnable {
            if (mStopJackDetect) {
                return false;
            }
            mAlsaDevice.updateWiredDeviceConnectionState(true);
            mAlsaDevice.updateOutputWiredDeviceConnectionState(true);
            mAlsaDevice.updateInputWiredDeviceConnectionState(true);
        }
        return true;
    }
+108 −39
Original line number Diff line number Diff line
@@ -20,12 +20,14 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.hardware.usb.UsbDevice;
import android.media.AudioManager;
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.os.SystemProperties;
import android.provider.Settings;
import android.service.usb.UsbAlsaManagerProto;
import android.util.Slog;
@@ -42,6 +44,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Stack;

/**
 * UsbAlsaManager manages USB audio and MIDI devices.
@@ -51,8 +54,9 @@ public final class UsbAlsaManager {
    private static final boolean DEBUG = false;

    // Flag to turn on/off multi-peripheral select mode
    // Set to true to have single-device-only mode
    private static final boolean mIsSingleMode = true;
    // Set to true to have multi-devices mode
    private static final boolean IS_MULTI_MODE = SystemProperties.getBoolean(
            "ro.audio.multi_usb_mode", false /*def*/);

    private static final String ALSA_DIRECTORY = "/dev/snd/";

@@ -70,7 +74,11 @@ public final class UsbAlsaManager {
    // this is needed to map USB devices to ALSA Audio Devices, especially to remove an
    // ALSA device when we are notified that its associated USB device has been removed.
    private final ArrayList<UsbAlsaDevice> mAlsaDevices = new ArrayList<UsbAlsaDevice>();
    private UsbAlsaDevice mSelectedDevice;
    // A map from device type to attached devices. Given the audio framework only supports
    // single device connection per device type, only the last attached device will be
    // connected to audio framework. Once the last device is removed, previous device can
    // be connected to audio framework.
    private HashMap<Integer, Stack<UsbAlsaDevice>> mAttachedDevices = new HashMap<>();

    //
    // Device Denylist
@@ -162,11 +170,6 @@ public final class UsbAlsaManager {
            Slog.d(TAG, "selectAlsaDevice() " + alsaDevice);
        }

        // This must be where an existing USB audio device is deselected.... (I think)
        if (mIsSingleMode && mSelectedDevice != null) {
            deselectAlsaDevice();
        }

        // FIXME Does not yet handle the case where the setting is changed
        // after device connection.  Ideally we should handle the settings change
        // in SettingsObserver. Here we should log that a USB device is connected
@@ -178,21 +181,18 @@ public final class UsbAlsaManager {
            return;
        }

        mSelectedDevice = alsaDevice;
        alsaDevice.start();

        if (DEBUG) {
            Slog.d(TAG, "selectAlsaDevice() - done.");
        }
    }

    private synchronized void deselectAlsaDevice() {
    private synchronized void deselectAlsaDevice(UsbAlsaDevice selectedDevice) {
        if (DEBUG) {
            Slog.d(TAG, "deselectAlsaDevice() mSelectedDevice " + mSelectedDevice);
        }
        if (mSelectedDevice != null) {
            mSelectedDevice.stop();
            mSelectedDevice = null;
            Slog.d(TAG, "deselectAlsaDevice() selectedDevice " + selectedDevice);
        }
        selectedDevice.stop();
    }

    private int getAlsaDeviceListIndexFor(String deviceAddress) {
@@ -204,31 +204,85 @@ public final class UsbAlsaManager {
        return -1;
    }

    private UsbAlsaDevice removeAlsaDeviceFromList(String deviceAddress) {
    private void addDeviceToAttachedDevicesMap(int deviceType, UsbAlsaDevice device) {
        if (deviceType == AudioManager.DEVICE_NONE) {
            Slog.i(TAG, "Ignore caching device as the type is NONE, device=" + device);
            return;
        }
        Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
        if (devices == null) {
            mAttachedDevices.put(deviceType, new Stack<>());
            devices = mAttachedDevices.get(deviceType);
        }
        devices.push(device);
    }

    private void addAlsaDevice(UsbAlsaDevice device) {
        mAlsaDevices.add(0, device);
        addDeviceToAttachedDevicesMap(device.getInputDeviceType(), device);
        addDeviceToAttachedDevicesMap(device.getOutputDeviceType(), device);
    }

    private void removeDeviceFromAttachedDevicesMap(int deviceType, UsbAlsaDevice device) {
        Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
        if (devices == null) {
            return;
        }
        devices.remove(device);
        if (devices.isEmpty()) {
            mAttachedDevices.remove(deviceType);
        }
    }

    private UsbAlsaDevice removeAlsaDevice(String deviceAddress) {
        int index = getAlsaDeviceListIndexFor(deviceAddress);
        if (index > -1) {
            return mAlsaDevices.remove(index);
            UsbAlsaDevice device = mAlsaDevices.remove(index);
            removeDeviceFromAttachedDevicesMap(device.getOutputDeviceType(), device);
            removeDeviceFromAttachedDevicesMap(device.getInputDeviceType(), device);
            return device;
        } else {
            return null;
        }
    }

    /* package */ UsbAlsaDevice selectDefaultDevice() {
    private UsbAlsaDevice selectDefaultDevice(int deviceType) {
        if (DEBUG) {
            Slog.d(TAG, "selectDefaultDevice()");
            Slog.d(TAG, "selectDefaultDevice():" + deviceType);
        }

        if (mAlsaDevices.size() > 0) {
            UsbAlsaDevice alsaDevice = mAlsaDevices.get(0);
            if (DEBUG) {
                Slog.d(TAG, "  alsaDevice:" + alsaDevice);
        Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
        if (devices == null || devices.isEmpty()) {
            return null;
        }
            if (alsaDevice != null) {
                selectAlsaDevice(alsaDevice);
        UsbAlsaDevice alsaDevice = devices.peek();
        Slog.d(TAG, "select default device:" + alsaDevice);
        if (AudioManager.isInputDevice(deviceType)) {
            alsaDevice.startInput();
        } else {
            alsaDevice.startOutput();
        }
        return alsaDevice;
    }

    private void deselectCurrentDevice(int deviceType) {
        if (DEBUG) {
            Slog.d(TAG, "deselectCurrentDevice():" + deviceType);
        }
        if (deviceType == AudioManager.DEVICE_NONE) {
            return;
        }

        Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
        if (devices == null || devices.isEmpty()) {
            return;
        }
        UsbAlsaDevice alsaDevice = devices.peek();
        Slog.d(TAG, "deselect current device:" + alsaDevice);
        if (AudioManager.isInputDevice(deviceType)) {
            alsaDevice.stopInput();
        } else {
            return null;
            alsaDevice.stopOutput();
        }
    }

@@ -246,6 +300,7 @@ public final class UsbAlsaManager {
        AlsaCardsParser.AlsaCardRecord cardRec =
                mCardsParser.findCardNumFor(deviceAddress);
        if (cardRec == null) {
            Slog.e(TAG, "usbDeviceAdded(): cannot find sound card for " + deviceAddress);
            return;
        }

@@ -275,13 +330,20 @@ public final class UsbAlsaManager {
                    new UsbAlsaDevice(mAudioService, cardRec.getCardNum(), 0 /*device*/,
                                      deviceAddress, hasOutput, hasInput,
                                      isInputHeadset, isOutputHeadset, isDock);
            if (alsaDevice != null) {
            alsaDevice.setDeviceNameAndDescription(
                      cardRec.getCardName(), cardRec.getCardDescription());
                mAlsaDevices.add(0, alsaDevice);
                selectAlsaDevice(alsaDevice);
            if (IS_MULTI_MODE) {
                deselectCurrentDevice(alsaDevice.getInputDeviceType());
                deselectCurrentDevice(alsaDevice.getOutputDeviceType());
            } else {
                // At single mode, the first device is the selected device.
                if (!mAlsaDevices.isEmpty()) {
                    deselectAlsaDevice(mAlsaDevices.get(0));
                }
            }
            addAlsaDevice(alsaDevice);
            selectAlsaDevice(alsaDevice);
        }

        addMidiDevice(deviceAddress, usbDevice, parser, cardRec);

@@ -346,12 +408,20 @@ public final class UsbAlsaManager {
        }

        // Audio
        UsbAlsaDevice alsaDevice = removeAlsaDeviceFromList(deviceAddress);
        UsbAlsaDevice alsaDevice = removeAlsaDevice(deviceAddress);
        Slog.i(TAG, "USB Audio Device Removed: " + alsaDevice);
        if (alsaDevice != null && alsaDevice == mSelectedDevice) {
        if (alsaDevice != null) {
            waitForAlsaDevice(alsaDevice.getCardNum(), false /*isAdded*/);
            deselectAlsaDevice();
            selectDefaultDevice(); // if there any external devices left, select one of them
            deselectAlsaDevice(alsaDevice);
            if (IS_MULTI_MODE) {
                selectDefaultDevice(alsaDevice.getOutputDeviceType());
                selectDefaultDevice(alsaDevice.getInputDeviceType());
            } else {
                // If there are any external devices left, select the latest attached one
                if (!mAlsaDevices.isEmpty() && mAlsaDevices.get(0) != null) {
                    selectAlsaDevice(mAlsaDevices.get(0));
                }
            }
        }

        // MIDI
@@ -362,7 +432,6 @@ public final class UsbAlsaManager {
        }

        logDevices("usbDeviceRemoved()");

    }

   /* package */ void setPeripheralMidiState(boolean enabled, int card, int device) {