diff options
3 files changed, 281 insertions, 125 deletions
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java index 337e1f92050c..7fe8582f96de 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java @@ -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); - } - 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); - } - } + 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; + } - // 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 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]; + } + + /** + * @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; } - } catch (RemoteException e) { - Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState"); } + 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; diff --git a/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java b/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java index c4988478df71..d4f0b59dd7f2 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java @@ -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; } diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java index aa1d556d02d3..99881e194b07 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java @@ -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) { |