Loading services/usb/java/com/android/server/usb/UsbAlsaDevice.java +171 −85 Original line number Diff line number Diff line Loading @@ -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. */ Loading @@ -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; Loading @@ -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(); } /** Loading Loading @@ -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]; } /** Loading Loading @@ -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); } Loading @@ -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 Loading @@ -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] + "]"; } /** Loading @@ -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); Loading Loading @@ -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); } Loading @@ -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; Loading services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java +2 −1 Original line number Diff line number Diff line Loading @@ -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; } Loading services/usb/java/com/android/server/usb/UsbAlsaManager.java +108 −39 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading @@ -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/"; Loading @@ -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 Loading Loading @@ -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 Loading @@ -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) { Loading @@ -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(); } } Loading @@ -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; } Loading Loading @@ -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); Loading Loading @@ -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 Loading @@ -362,7 +432,6 @@ public final class UsbAlsaManager { } logDevices("usbDeviceRemoved()"); } /* package */ void setPeripheralMidiState(boolean enabled, int card, int device) { Loading Loading
services/usb/java/com/android/server/usb/UsbAlsaDevice.java +171 −85 Original line number Diff line number Diff line Loading @@ -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. */ Loading @@ -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; Loading @@ -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(); } /** Loading Loading @@ -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]; } /** Loading Loading @@ -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); } Loading @@ -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 Loading @@ -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] + "]"; } /** Loading @@ -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); Loading Loading @@ -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); } Loading @@ -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; Loading
services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java +2 −1 Original line number Diff line number Diff line Loading @@ -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; } Loading
services/usb/java/com/android/server/usb/UsbAlsaManager.java +108 −39 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading @@ -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/"; Loading @@ -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 Loading Loading @@ -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 Loading @@ -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) { Loading @@ -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(); } } Loading @@ -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; } Loading Loading @@ -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); Loading Loading @@ -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 Loading @@ -362,7 +432,6 @@ public final class UsbAlsaManager { } logDevices("usbDeviceRemoved()"); } /* package */ void setPeripheralMidiState(boolean enabled, int card, int device) { Loading