Merge "Multiple USB support." into udc-dev

This commit is contained in:
Treehugger Robot
2023-04-11 18:58:42 +00:00
committed by Android (Google) Code Review
3 changed files with 289 additions and 133 deletions

View File

@@ -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;
// 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 final boolean[] mIsHeadset = new boolean[NUM_DIRECTIONS];
private final boolean mIsDock;
private boolean mSelected = false;
private int mOutputState;
private int mInputState;
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 true if the device is a headset for purposes of input.
*/
public boolean isInputHeadset() {
return mIsInputHeadset;
return mHasDevice[INPUT];
}
/**
* @return true if the device is a headset for purposes of output.
*/
public boolean isOutputHeadset() {
return mIsOutputHeadset;
return mIsHeadset[OUTPUT];
}
/**
* @return true if the device is a headset for purposes of input.
*/
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.
/** Stop using this device as the selected USB input device. */
public synchronized void stopInput() {
if (!mIsSelected[INPUT]) {
return;
}
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;
}
if (!mIsSelected[INPUT]) {
// Stop jack detection when both input and output are stopped
stopJackDetect();
}
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 synchronized void updateWiredDeviceConnectionState(boolean enable) {
if (!mSelected) {
Slog.e(TAG, "updateWiredDeviceConnectionState on unselected AlsaDevice!");
return;
}
String alsaCardDeviceString = getAlsaCardDeviceString();
if (alsaCardDeviceString == null) {
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);
}
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);
}
}
// 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);
}
}
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState");
}
public int getOutputDeviceType() {
return mDeviceType[OUTPUT];
}
/**
* @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 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;

View File

@@ -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;
}

View File

@@ -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,32 +204,86 @@ 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);
}
if (alsaDevice != null) {
selectAlsaDevice(alsaDevice);
}
return alsaDevice;
} else {
Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
if (devices == null || devices.isEmpty()) {
return null;
}
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 {
alsaDevice.stopOutput();
}
}
/* package */ void usbDeviceAdded(String deviceAddress, UsbDevice usbDevice,
@@ -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,12 +330,19 @@ 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);
alsaDevice.setDeviceNameAndDescription(
cardRec.getCardName(), cardRec.getCardDescription());
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) {