diff options
| author | 2023-04-17 10:34:45 +0200 | |
|---|---|---|
| committer | 2023-04-28 11:40:07 +0200 | |
| commit | 4aaeb735d65c202a42dcd46e5c476fed7185a88a (patch) | |
| tree | 59b2d2b9888492df4317563ed2124270520717e2 | |
| parent | ced3c22d90d579663199d57c0de5310c674c6a8f (diff) | |
AudioService: BT dual mode support
Add support for mode selection per audio profile for connected
Bluetooth devices.
The modes are retrieved from BluetoothDevice.getPreferredAudioProfiles()
and used to enable or disable a particular device for use by routing
strategies associated to full duplex or playback only operations.
The BT Stack will ensure that the supported modes/profiles for a given dual
mode device are consistent (for instance LE Audio and A2DP cannot be
enabled at the same time for OUTPUT_ONLY mode).
Bug: 265077417
Test: connect dual mode Bluetooth headset and switch profiles
Change-Id: Ibc654b4129aa809f9c03892a2c8971fb2e0825c4
4 files changed, 512 insertions, 183 deletions
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index e73cf87ba9f3..3123ee6dd4d7 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1237,6 +1237,9 @@ public class AudioSystem public static final Set<Integer> DEVICE_IN_ALL_SCO_SET; /** @hide */ public static final Set<Integer> DEVICE_IN_ALL_USB_SET; + /** @hide */ + public static final Set<Integer> DEVICE_IN_ALL_BLE_SET; + static { DEVICE_IN_ALL_SET = new HashSet<>(); DEVICE_IN_ALL_SET.add(DEVICE_IN_COMMUNICATION); @@ -1276,6 +1279,66 @@ public class AudioSystem DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_ACCESSORY); DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_DEVICE); DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_HEADSET); + + DEVICE_IN_ALL_BLE_SET = new HashSet<>(); + DEVICE_IN_ALL_BLE_SET.add(DEVICE_IN_BLE_HEADSET); + } + + /** @hide */ + public static boolean isBluetoothDevice(int deviceType) { + return isBluetoothA2dpOutDevice(deviceType) + || isBluetoothScoDevice(deviceType) + || isBluetoothLeDevice(deviceType); + } + + /** @hide */ + public static boolean isBluetoothOutDevice(int deviceType) { + return isBluetoothA2dpOutDevice(deviceType) + || isBluetoothScoOutDevice(deviceType) + || isBluetoothLeOutDevice(deviceType); + } + + /** @hide */ + public static boolean isBluetoothInDevice(int deviceType) { + return isBluetoothScoInDevice(deviceType) + || isBluetoothLeInDevice(deviceType); + } + + /** @hide */ + public static boolean isBluetoothA2dpOutDevice(int deviceType) { + return DEVICE_OUT_ALL_A2DP_SET.contains(deviceType); + } + + /** @hide */ + public static boolean isBluetoothScoOutDevice(int deviceType) { + return DEVICE_OUT_ALL_SCO_SET.contains(deviceType); + } + + /** @hide */ + public static boolean isBluetoothScoInDevice(int deviceType) { + return DEVICE_IN_ALL_SCO_SET.contains(deviceType); + } + + /** @hide */ + public static boolean isBluetoothScoDevice(int deviceType) { + return isBluetoothScoOutDevice(deviceType) + || isBluetoothScoInDevice(deviceType); + } + + /** @hide */ + public static boolean isBluetoothLeOutDevice(int deviceType) { + return DEVICE_OUT_ALL_BLE_SET.contains(deviceType); + } + + /** @hide */ + public static boolean isBluetoothLeInDevice(int deviceType) { + return DEVICE_IN_ALL_BLE_SET.contains(deviceType); + } + + /** @hide */ + public static boolean isBluetoothLeDevice(int deviceType) { + return isBluetoothLeOutDevice(deviceType) + || isBluetoothLeInDevice(deviceType); } /** @hide */ diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index c879fa64ff02..bc4e8df2a4ad 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -88,14 +88,14 @@ import java.util.concurrent.atomic.AtomicBoolean; private final @NonNull AudioSystemAdapter mAudioSystem; /** ID for Communication strategy retrieved form audio policy manager */ - private int mCommunicationStrategyId = -1; + /*package*/ int mCommunicationStrategyId = -1; /** ID for Accessibility strategy retrieved form audio policy manager */ private int mAccessibilityStrategyId = -1; /** Active communication device reported by audio policy manager */ - private AudioDeviceInfo mActiveCommunicationDevice; + /*package*/ AudioDeviceInfo mActiveCommunicationDevice; /** Last preferred device set for communication strategy */ private AudioDeviceAttributes mPreferredCommunicationDevice; @@ -755,6 +755,19 @@ import java.util.concurrent.atomic.AtomicBoolean; mIsLeOutput = false; } + BtDeviceInfo(@NonNull BtDeviceInfo src, int state) { + mDevice = src.mDevice; + mState = state; + mProfile = src.mProfile; + mSupprNoisy = src.mSupprNoisy; + mVolume = src.mVolume; + mIsLeOutput = src.mIsLeOutput; + mEventSource = src.mEventSource; + mAudioSystemDevice = src.mAudioSystemDevice; + mMusicDevice = src.mMusicDevice; + mCodec = src.mCodec; + } + // redefine equality op so we can match messages intended for this device @Override public boolean equals(Object o) { @@ -821,7 +834,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * @param info struct with the (dis)connection information */ /*package*/ void queueOnBluetoothActiveDeviceChanged(@NonNull BtDeviceChangedData data) { - if (data.mInfo.getProfile() == BluetoothProfile.A2DP && data.mPreviousDevice != null + if (data.mPreviousDevice != null && data.mPreviousDevice.equals(data.mNewDevice)) { final String name = TextUtils.emptyIfNull(data.mNewDevice.getName()); new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE + MediaMetrics.SEPARATOR @@ -830,7 +843,8 @@ import java.util.concurrent.atomic.AtomicBoolean; .set(MediaMetrics.Property.STATUS, data.mInfo.getProfile()) .record(); synchronized (mDeviceStateLock) { - postBluetoothA2dpDeviceConfigChange(data.mNewDevice); + postBluetoothDeviceConfigChange(createBtDeviceInfo(data, data.mNewDevice, + BluetoothProfile.STATE_CONNECTED)); } } else { synchronized (mDeviceStateLock) { @@ -1064,8 +1078,8 @@ import java.util.concurrent.atomic.AtomicBoolean; new AudioModeInfo(mode, pid, uid)); } - /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) { - sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device); + /*package*/ void postBluetoothDeviceConfigChange(@NonNull BtDeviceInfo info) { + sendLMsgNoDelay(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, info); } /*package*/ void startBluetoothScoForClient(IBinder cb, int pid, int scoAudioMode, @@ -1322,6 +1336,10 @@ import java.util.concurrent.atomic.AtomicBoolean; sendIMsgNoDelay(MSG_I_SCO_AUDIO_STATE_CHANGED, SENDMSG_QUEUE, state); } + /*package*/ void postNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice) { + sendLMsgNoDelay(MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED, SENDMSG_QUEUE, btDevice); + } + /*package*/ static final class CommunicationDeviceInfo { final @NonNull IBinder mCb; // Identifies the requesting client for death handler final int mPid; // Requester process ID @@ -1397,9 +1415,11 @@ import java.util.concurrent.atomic.AtomicBoolean; } } - /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect) { + /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, + boolean connect, @Nullable BluetoothDevice btDevice) { synchronized (mDeviceStateLock) { - return mDeviceInventory.handleDeviceConnection(attributes, connect, false /*for test*/); + return mDeviceInventory.handleDeviceConnection( + attributes, connect, false /*for test*/, btDevice); } } @@ -1640,13 +1660,10 @@ import java.util.concurrent.atomic.AtomicBoolean; (String) msg.obj, msg.arg1); } break; - case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: - final BluetoothDevice btDevice = (BluetoothDevice) msg.obj; + case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: synchronized (mDeviceStateLock) { - final int a2dpCodec = mBtHelper.getA2dpCodec(btDevice); - mDeviceInventory.onBluetoothA2dpDeviceConfigChange( - new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec), - BtHelper.EVENT_DEVICE_CONFIG_CHANGE); + mDeviceInventory.onBluetoothDeviceConfigChange( + (BtDeviceInfo) msg.obj, BtHelper.EVENT_DEVICE_CONFIG_CHANGE); } break; case MSG_BROADCAST_AUDIO_BECOMING_NOISY: @@ -1810,6 +1827,10 @@ import java.util.concurrent.atomic.AtomicBoolean; case MSG_IL_SET_LEAUDIO_SUSPENDED: { setLeAudioSuspended((msg.arg1 == 1), false /*internal*/, (String) msg.obj); } break; + case MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED: { + final BluetoothDevice btDevice = (BluetoothDevice) msg.obj; + BtHelper.onNotifyPreferredAudioProfileApplied(btDevice); + } break; default: Log.wtf(TAG, "Invalid message " + msg.what); } @@ -1845,7 +1866,7 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final int MSG_IL_BTA2DP_TIMEOUT = 10; // process change of A2DP device configuration, obj is BluetoothDevice - private static final int MSG_L_A2DP_DEVICE_CONFIG_CHANGE = 11; + private static final int MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE = 11; private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12; private static final int MSG_REPORT_NEW_ROUTES = 13; @@ -1887,13 +1908,15 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final int MSG_IL_SET_A2DP_SUSPENDED = 50; private static final int MSG_IL_SET_LEAUDIO_SUSPENDED = 51; + private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52; + private static boolean isMessageHandledUnderWakelock(int msgId) { switch(msgId) { case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: case MSG_L_SET_BT_ACTIVE_DEVICE: case MSG_IL_BTA2DP_TIMEOUT: case MSG_IL_BTLEAUDIO_TIMEOUT: - case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: + case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: case MSG_TOGGLE_HDMI: case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT: case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT: @@ -1985,7 +2008,7 @@ import java.util.concurrent.atomic.AtomicBoolean; case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: case MSG_IL_BTA2DP_TIMEOUT: case MSG_IL_BTLEAUDIO_TIMEOUT: - case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: + case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: if (sLastDeviceConnectMsgTime >= time) { // add a little delay to make sure messages are ordered as expected time = sLastDeviceConnectMsgTime + 30; @@ -2005,7 +2028,7 @@ import java.util.concurrent.atomic.AtomicBoolean; static { MESSAGES_MUTE_MUSIC = new HashSet<>(); MESSAGES_MUTE_MUSIC.add(MSG_L_SET_BT_ACTIVE_DEVICE); - MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONFIG_CHANGE); + MESSAGES_MUTE_MUSIC.add(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE); MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT); MESSAGES_MUTE_MUSIC.add(MSG_IIL_SET_FORCE_BT_A2DP_USE); } @@ -2026,7 +2049,7 @@ import java.util.concurrent.atomic.AtomicBoolean; // Do not mute on bluetooth event if music is playing on a wired headset. if ((message == MSG_L_SET_BT_ACTIVE_DEVICE || message == MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT - || message == MSG_L_A2DP_DEVICE_CONFIG_CHANGE) + || message == MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE) && AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) && hasIntersection(mDeviceInventory.DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET, mAudioService.getDeviceSetForStream(AudioSystem.STREAM_MUSIC))) { @@ -2173,6 +2196,7 @@ import java.util.concurrent.atomic.AtomicBoolean; mDeviceInventory.removePreferredDevicesForStrategy(mCommunicationStrategyId); mDeviceInventory.removePreferredDevicesForStrategy(mAccessibilityStrategyId); } + mDeviceInventory.applyConnectedDevicesRoles(); } else { mDeviceInventory.setPreferredDevicesForStrategy( mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice)); diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 94d796bff609..773df3720ed3 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -34,11 +34,15 @@ import android.media.ICapturePresetDevicesRoleDispatcher; import android.media.IStrategyNonDefaultDevicesDispatcher; import android.media.IStrategyPreferredDevicesDispatcher; import android.media.MediaMetrics; +import android.media.MediaRecorder.AudioSource; +import android.media.audiopolicy.AudioProductStrategy; import android.media.permission.ClearCallingIdentityContext; import android.media.permission.SafeCloseable; import android.os.Binder; +import android.os.Bundle; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.SystemProperties; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -50,6 +54,8 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.utils.EventLogger; +import com.google.android.collect.Sets; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -179,6 +185,8 @@ public class AudioDeviceInventory { final RemoteCallbackList<ICapturePresetDevicesRoleDispatcher> mDevRoleCapturePresetDispatchers = new RemoteCallbackList<ICapturePresetDevicesRoleDispatcher>(); + final List<AudioProductStrategy> mStrategies; + /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) { this(broker, AudioSystemAdapter.getDefaultAdapter()); } @@ -193,8 +201,10 @@ public class AudioDeviceInventory { @Nullable AudioSystemAdapter audioSystem) { mDeviceBroker = broker; mAudioSystem = audioSystem; + mStrategies = AudioProductStrategy.getAudioProductStrategies(); + mBluetoothDualModeEnabled = SystemProperties.getBoolean( + "persist.bluetooth.enable_dual_mode_audio", false); } - /*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) { mDeviceBroker = broker; } @@ -211,8 +221,13 @@ public class AudioDeviceInventory { int mDeviceCodecFormat; final UUID mSensorUuid; + /** Disabled operating modes for this device. Use a negative logic so that by default + * an empty list means all modes are allowed. + * See BluetoothAdapter.AUDIO_MODE_DUPLEX and BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY */ + @NonNull ArraySet<String> mDisabledModes = new ArraySet(0); + DeviceInfo(int deviceType, String deviceName, String deviceAddress, - int deviceCodecFormat, UUID sensorUuid) { + int deviceCodecFormat, @Nullable UUID sensorUuid) { mDeviceType = deviceType; mDeviceName = deviceName == null ? "" : deviceName; mDeviceAddress = deviceAddress == null ? "" : deviceAddress; @@ -220,11 +235,31 @@ public class AudioDeviceInventory { mSensorUuid = sensorUuid; } + void setModeDisabled(String mode) { + mDisabledModes.add(mode); + } + void setModeEnabled(String mode) { + mDisabledModes.remove(mode); + } + boolean isModeEnabled(String mode) { + return !mDisabledModes.contains(mode); + } + boolean isOutputOnlyModeEnabled() { + return isModeEnabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY); + } + boolean isDuplexModeEnabled() { + return isModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX); + } + DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) { this(deviceType, deviceName, deviceAddress, deviceCodecFormat, null); } + DeviceInfo(int deviceType, String deviceName, String deviceAddress) { + this(deviceType, deviceName, deviceAddress, AudioSystem.AUDIO_FORMAT_DEFAULT); + } + @Override public String toString() { return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType) @@ -232,7 +267,8 @@ public class AudioDeviceInventory { + ") name:" + mDeviceName + " addr:" + mDeviceAddress + " codec: " + Integer.toHexString(mDeviceCodecFormat) - + " sensorUuid: " + Objects.toString(mSensorUuid) + "]"; + + " sensorUuid: " + Objects.toString(mSensorUuid) + + " disabled modes: " + mDisabledModes + "]"; } @NonNull String getKey() { @@ -317,6 +353,7 @@ public class AudioDeviceInventory { di.mDeviceCodecFormat); } mAppliedStrategyRoles.clear(); + applyConnectedDevicesRoles_l(); } synchronized (mPreferredDevices) { mPreferredDevices.forEach((strategy, devices) -> { @@ -397,8 +434,7 @@ public class AudioDeviceInventory { btInfo.mVolume * 10, btInfo.mAudioSystemDevice, "onSetBtActiveDevice"); } - makeA2dpDeviceAvailable(address, BtHelper.getName(btInfo.mDevice), - "onSetBtActiveDevice", btInfo.mCodec); + makeA2dpDeviceAvailable(btInfo, "onSetBtActiveDevice"); } break; case BluetoothProfile.HEARING_AID: @@ -414,10 +450,7 @@ public class AudioDeviceInventory { if (switchToUnavailable) { makeLeAudioDeviceUnavailableNow(address, btInfo.mAudioSystemDevice); } else if (switchToAvailable) { - makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice), - streamType, btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10, - btInfo.mAudioSystemDevice, - "onSetBtActiveDevice"); + makeLeAudioDeviceAvailable(btInfo, streamType, "onSetBtActiveDevice"); } break; default: throw new IllegalArgumentException("Invalid profile " @@ -428,30 +461,30 @@ public class AudioDeviceInventory { @GuardedBy("AudioDeviceBroker.mDeviceStateLock") - /*package*/ void onBluetoothA2dpDeviceConfigChange( - @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event) { + /*package*/ void onBluetoothDeviceConfigChange( + @NonNull AudioDeviceBroker.BtDeviceInfo btInfo, int event) { MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId - + "onBluetoothA2dpDeviceConfigChange") - .set(MediaMetrics.Property.EVENT, BtHelper.a2dpDeviceEventToString(event)); + + "onBluetoothDeviceConfigChange") + .set(MediaMetrics.Property.EVENT, BtHelper.deviceEventToString(event)); - final BluetoothDevice btDevice = btInfo.getBtDevice(); + final BluetoothDevice btDevice = btInfo.mDevice; if (btDevice == null) { mmi.set(MediaMetrics.Property.EARLY_RETURN, "btDevice null").record(); return; } if (AudioService.DEBUG_DEVICES) { - Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice); + Log.d(TAG, "onBluetoothDeviceConfigChange btDevice=" + btDevice); } - int a2dpVolume = btInfo.getVolume(); - @AudioSystem.AudioFormatNativeEnumForBtCodec final int a2dpCodec = btInfo.getCodec(); + int volume = btInfo.mVolume; + @AudioSystem.AudioFormatNativeEnumForBtCodec final int audioCodec = btInfo.mCodec; String address = btDevice.getAddress(); if (!BluetoothAdapter.checkBluetoothAddress(address)) { address = ""; } AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( - "onBluetoothA2dpDeviceConfigChange addr=" + address - + " event=" + BtHelper.a2dpDeviceEventToString(event))); + "onBluetoothDeviceConfigChange addr=" + address + + " event=" + BtHelper.deviceEventToString(event))); synchronized (mDevicesLock) { if (mDeviceBroker.hasScheduledA2dpConnection(btDevice)) { @@ -466,53 +499,53 @@ public class AudioDeviceInventory { AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); final DeviceInfo di = mConnectedDevices.get(key); if (di == null) { - Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpDeviceConfigChange"); + Log.e(TAG, "invalid null DeviceInfo in onBluetoothDeviceConfigChange"); mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record(); return; } mmi.set(MediaMetrics.Property.ADDRESS, address) .set(MediaMetrics.Property.ENCODING, - AudioSystem.audioFormatToString(a2dpCodec)) - .set(MediaMetrics.Property.INDEX, a2dpVolume) + AudioSystem.audioFormatToString(audioCodec)) + .set(MediaMetrics.Property.INDEX, volume) .set(MediaMetrics.Property.NAME, di.mDeviceName); - if (event == BtHelper.EVENT_ACTIVE_DEVICE_CHANGE) { - // Device is connected - if (a2dpVolume != -1) { - mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC, - // convert index to internal representation in VolumeStreamState - a2dpVolume * 10, - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - "onBluetoothA2dpDeviceConfigChange"); - } - } else if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) { - if (di.mDeviceCodecFormat != a2dpCodec) { - di.mDeviceCodecFormat = a2dpCodec; - mConnectedDevices.replace(key, di); - } - } - final int res = mAudioSystem.handleDeviceConfigChange( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, - BtHelper.getName(btDevice), a2dpCodec); - if (res != AudioSystem.AUDIO_STATUS_OK) { - AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( - "APM handleDeviceConfigChange failed for A2DP device addr=" + address - + " codec=" + AudioSystem.audioFormatToString(a2dpCodec)) - .printLog(TAG)); + if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) { + boolean a2dpCodecChange = false; + if (btInfo.mProfile == BluetoothProfile.A2DP) { + if (di.mDeviceCodecFormat != audioCodec) { + di.mDeviceCodecFormat = audioCodec; + mConnectedDevices.replace(key, di); + a2dpCodecChange = true; + } + final int res = mAudioSystem.handleDeviceConfigChange( + btInfo.mAudioSystemDevice, address, + BtHelper.getName(btDevice), audioCodec); + + if (res != AudioSystem.AUDIO_STATUS_OK) { + AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( + "APM handleDeviceConfigChange failed for A2DP device addr=" + + address + " codec=" + + AudioSystem.audioFormatToString(audioCodec)) + .printLog(TAG)); + + // force A2DP device disconnection in case of error so that AudioService + // state is consistent with audio policy manager state + setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btInfo, + BluetoothProfile.STATE_DISCONNECTED)); + } else { + AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( + "APM handleDeviceConfigChange success for A2DP device addr=" + + address + + " codec=" + AudioSystem.audioFormatToString(audioCodec)) + .printLog(TAG)); - int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); - // force A2DP device disconnection in case of error so that AudioService state is - // consistent with audio policy manager state - setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btDevice, - BluetoothProfile.A2DP, BluetoothProfile.STATE_DISCONNECTED, - musicDevice, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)); - } else { - AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( - "APM handleDeviceConfigChange success for A2DP device addr=" + address - + " codec=" + AudioSystem.audioFormatToString(a2dpCodec)) - .printLog(TAG)); + } + } + if (!a2dpCodecChange) { + updateBluetoothPreferredModes_l(btDevice /*connectedDevice*/); + } } } mmi.record(); @@ -595,7 +628,7 @@ public class AudioDeviceInventory { } if (!handleDeviceConnection(wdcs.mAttributes, - wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest)) { + wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest, null)) { // change of connection state failed, bailout mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed") .record(); @@ -1134,10 +1167,11 @@ public class AudioDeviceInventory { * @param connect true if connection * @param isForTesting if true, not calling AudioSystem for the connection as this is * just for testing + * @param btDevice the corresponding Bluetooth device when relevant. * @return false if an error was reported by AudioSystem */ /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect, - boolean isForTesting) { + boolean isForTesting, @Nullable BluetoothDevice btDevice) { int device = attributes.getInternalType(); String address = attributes.getAddress(); String deviceName = attributes.getName(); @@ -1152,6 +1186,7 @@ public class AudioDeviceInventory { .set(MediaMetrics.Property.MODE, connect ? MediaMetrics.Value.CONNECT : MediaMetrics.Value.DISCONNECT) .set(MediaMetrics.Property.NAME, deviceName); + boolean status = false; synchronized (mDevicesLock) { final String deviceKey = DeviceInfo.makeDeviceListKey(device, address); if (AudioService.DEBUG_DEVICES) { @@ -1179,25 +1214,31 @@ public class AudioDeviceInventory { .record(); return false; } - mConnectedDevices.put(deviceKey, new DeviceInfo( - device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); + mConnectedDevices.put(deviceKey, new DeviceInfo(device, deviceName, address)); mDeviceBroker.postAccessoryPlugMediaUnmute(device); - mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record(); - return true; + status = true; } else if (!connect && isConnected) { mAudioSystem.setDeviceConnectionState(attributes, AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); // always remove even if disconnection failed mConnectedDevices.remove(deviceKey); - purgeDevicesRoles_l(); + status = true; + } + if (status) { + if (AudioSystem.isBluetoothScoDevice(device)) { + updateBluetoothPreferredModes_l(connect ? btDevice : null /*connectedDevice*/); + if (!connect) { + purgeDevicesRoles_l(); + } + } mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record(); - return true; + } else { + Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey + + ", deviceSpec=" + di + ", connect=" + connect); + mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record(); } - Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey - + ", deviceSpec=" + di + ", connect=" + connect); } - mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record(); - return false; + return status; } @@ -1406,15 +1447,20 @@ public class AudioDeviceInventory { // Internal utilities @GuardedBy("mDevicesLock") - private void makeA2dpDeviceAvailable(String address, String name, String eventSource, - int a2dpCodec) { + private void makeA2dpDeviceAvailable(AudioDeviceBroker.BtDeviceInfo btInfo, + String eventSource) { + final String address = btInfo.mDevice.getAddress(); + final String name = BtHelper.getName(btInfo.mDevice); + final int a2dpCodec = btInfo.mCodec; + // enable A2DP before notifying A2DP connection to avoid unnecessary processing in // audio policy manager mDeviceBroker.setBluetoothA2dpOnInt(true, true /*fromA2dp*/, eventSource); // at this point there could be another A2DP device already connected in APM, but it // doesn't matter as this new one will overwrite the previous one - final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name), + AudioDeviceAttributes ada = new AudioDeviceAttributes( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name); + final int res = mAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_AVAILABLE, a2dpCodec); // TODO: log in MediaMetrics once distinction between connection failure and @@ -1436,8 +1482,7 @@ public class AudioDeviceInventory { // The convention for head tracking sensors associated with A2DP devices is to // use a UUID derived from the MAC address as follows: // time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address - UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes( - new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address)); + UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada); final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name, address, a2dpCodec, sensorUuid); final String diKey = di.getKey(); @@ -1448,6 +1493,208 @@ public class AudioDeviceInventory { mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/); + + updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/); + } + + static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER, + AudioSource.VOICE_RECOGNITION, AudioSource.VOICE_COMMUNICATION, + AudioSource.UNPROCESSED, AudioSource.VOICE_PERFORMANCE, AudioSource.HOTWORD}; + + // reflects system property persist.bluetooth.enable_dual_mode_audio + final boolean mBluetoothDualModeEnabled; + /** + * Goes over all connected Bluetooth devices and set the audio policy device role to DISABLED + * or not according to their own and other devices modes. + * The top priority is given to LE devices, then SCO ,then A2DP. + */ + @GuardedBy("mDevicesLock") + private void applyConnectedDevicesRoles_l() { + if (!mBluetoothDualModeEnabled) { + return; + } + DeviceInfo leOutDevice = + getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_BLE_SET); + DeviceInfo leInDevice = + getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_BLE_SET); + DeviceInfo a2dpDevice = + getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_A2DP_SET); + DeviceInfo scoOutDevice = + getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_SCO_SET); + DeviceInfo scoInDevice = + getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_SCO_SET); + boolean disableA2dp = (leOutDevice != null && leOutDevice.isOutputOnlyModeEnabled()); + boolean disableSco = (leOutDevice != null && leOutDevice.isDuplexModeEnabled()) + || (leInDevice != null && leInDevice.isDuplexModeEnabled()); + AudioDeviceAttributes communicationDevice = + mDeviceBroker.mActiveCommunicationDevice == null + ? null : ((mDeviceBroker.isInCommunication() + && mDeviceBroker.mActiveCommunicationDevice != null) + ? new AudioDeviceAttributes(mDeviceBroker.mActiveCommunicationDevice) + : null); + + if (AudioService.DEBUG_DEVICES) { + Log.i(TAG, "applyConnectedDevicesRoles_l\n - leOutDevice: " + leOutDevice + + "\n - leInDevice: " + leInDevice + + "\n - a2dpDevice: " + a2dpDevice + + "\n - scoOutDevice: " + scoOutDevice + + "\n - scoInDevice: " + scoInDevice + + "\n - disableA2dp: " + disableA2dp + + ", disableSco: " + disableSco); + } + + for (DeviceInfo di : mConnectedDevices.values()) { + if (!AudioSystem.isBluetoothDevice(di.mDeviceType)) { + continue; + } + AudioDeviceAttributes ada = + new AudioDeviceAttributes(di.mDeviceType, di.mDeviceAddress, di.mDeviceName); + if (AudioService.DEBUG_DEVICES) { + Log.i(TAG, " + checking Device: " + ada); + } + if (ada.equalTypeAddress(communicationDevice)) { + continue; + } + + if (AudioSystem.isBluetoothOutDevice(di.mDeviceType)) { + for (AudioProductStrategy strategy : mStrategies) { + boolean disable = false; + if (strategy.getId() == mDeviceBroker.mCommunicationStrategyId) { + if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) { + disable = disableSco || !di.isDuplexModeEnabled(); + } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) { + disable = !di.isDuplexModeEnabled(); + } + } else { + if (AudioSystem.isBluetoothA2dpOutDevice(di.mDeviceType)) { + disable = disableA2dp || !di.isOutputOnlyModeEnabled(); + } else if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) { + disable = disableSco || !di.isOutputOnlyModeEnabled(); + } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) { + disable = !di.isOutputOnlyModeEnabled(); + } + } + if (AudioService.DEBUG_DEVICES) { + Log.i(TAG, " - strategy: " + strategy.getId() + + ", disable: " + disable); + } + if (disable) { + addDevicesRoleForStrategy(strategy.getId(), + AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada)); + } else { + removeDevicesRoleForStrategy(strategy.getId(), + AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada)); + } + } + } + if (AudioSystem.isBluetoothInDevice(di.mDeviceType)) { + for (int capturePreset : CAPTURE_PRESETS) { + boolean disable = false; + if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) { + disable = disableSco || !di.isDuplexModeEnabled(); + } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) { + disable = !di.isDuplexModeEnabled(); + } + if (AudioService.DEBUG_DEVICES) { + Log.i(TAG, " - capturePreset: " + capturePreset + + ", disable: " + disable); + } + if (disable) { + addDevicesRoleForCapturePreset(capturePreset, + AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada)); + } else { + removeDevicesRoleForCapturePreset(capturePreset, + AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada)); + } + } + } + } + } + + /* package */ void applyConnectedDevicesRoles() { + synchronized (mDevicesLock) { + applyConnectedDevicesRoles_l(); + } + } + + @GuardedBy("mDevicesLock") + int checkProfileIsConnected(int profile) { + switch (profile) { + case BluetoothProfile.HEADSET: + if (getFirstConnectedDeviceOfTypes( + AudioSystem.DEVICE_OUT_ALL_SCO_SET) != null + || getFirstConnectedDeviceOfTypes( + AudioSystem.DEVICE_IN_ALL_SCO_SET) != null) { + return profile; + } + break; + case BluetoothProfile.A2DP: + if (getFirstConnectedDeviceOfTypes( + AudioSystem.DEVICE_OUT_ALL_A2DP_SET) != null) { + return profile; + } + break; + case BluetoothProfile.LE_AUDIO: + case BluetoothProfile.LE_AUDIO_BROADCAST: + if (getFirstConnectedDeviceOfTypes( + AudioSystem.DEVICE_OUT_ALL_BLE_SET) != null + || getFirstConnectedDeviceOfTypes( + AudioSystem.DEVICE_IN_ALL_BLE_SET) != null) { + return profile; + } + break; + default: + break; + } + return 0; + } + + @GuardedBy("mDevicesLock") + private void updateBluetoothPreferredModes_l(BluetoothDevice connectedDevice) { + if (!mBluetoothDualModeEnabled) { + return; + } + HashSet<String> processedAddresses = new HashSet<>(0); + for (DeviceInfo di : mConnectedDevices.values()) { + if (!AudioSystem.isBluetoothDevice(di.mDeviceType) + || processedAddresses.contains(di.mDeviceAddress)) { + continue; + } + Bundle preferredProfiles = BtHelper.getPreferredAudioProfiles(di.mDeviceAddress); + if (AudioService.DEBUG_DEVICES) { + Log.i(TAG, "updateBluetoothPreferredModes_l processing device address: " + + di.mDeviceAddress + ", preferredProfiles: " + preferredProfiles); + } + for (DeviceInfo di2 : mConnectedDevices.values()) { + if (!AudioSystem.isBluetoothDevice(di2.mDeviceType) + || !di.mDeviceAddress.equals(di2.mDeviceAddress)) { + continue; + } + int profile = BtHelper.getProfileFromType(di2.mDeviceType); + if (profile == 0) { + continue; + } + int preferredProfile = checkProfileIsConnected( + preferredProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)); + if (preferredProfile == profile || preferredProfile == 0) { + di2.setModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX); + } else { + di2.setModeDisabled(BluetoothAdapter.AUDIO_MODE_DUPLEX); + } + preferredProfile = checkProfileIsConnected( + preferredProfiles.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY)); + if (preferredProfile == profile || preferredProfile == 0) { + di2.setModeEnabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY); + } else { + di2.setModeDisabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY); + } + } + processedAddresses.add(di.mDeviceAddress); + } + applyConnectedDevicesRoles_l(); + if (connectedDevice != null) { + mDeviceBroker.postNotifyPreferredAudioProfileApplied(connectedDevice); + } } @GuardedBy("mDevicesLock") @@ -1495,6 +1742,7 @@ public class AudioDeviceInventory { // Remove A2DP routes as well setCurrentAudioRouteNameIfPossible(null, true /*fromA2dp*/); mmi.record(); + updateBluetoothPreferredModes_l(null /*connectedDevice*/); purgeDevicesRoles_l(); } @@ -1525,8 +1773,7 @@ public class AudioDeviceInventory { AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.put( DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), - new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", - address, AudioSystem.AUDIO_FORMAT_DEFAULT)); + new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", address)); } @GuardedBy("mDevicesLock") @@ -1552,8 +1799,7 @@ public class AudioDeviceInventory { AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.put( DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address), - new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name, - address, AudioSystem.AUDIO_FORMAT_DEFAULT)); + new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name, address)); mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID); mDeviceBroker.postApplyVolumeOnDevice(streamType, AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable"); @@ -1591,29 +1837,56 @@ public class AudioDeviceInventory { * @return true if a DEVICE_OUT_HEARING_AID is connected, false otherwise. */ boolean isHearingAidConnected() { + return getFirstConnectedDeviceOfTypes( + Sets.newHashSet(AudioSystem.DEVICE_OUT_HEARING_AID)) != null; + } + + /** + * Returns a DeviceInfo for the first connected device matching one of the supplied types + */ + private DeviceInfo getFirstConnectedDeviceOfTypes(Set<Integer> internalTypes) { + List<DeviceInfo> devices = getConnectedDevicesOfTypes(internalTypes); + return devices.isEmpty() ? null : devices.get(0); + } + + /** + * Returns a list of connected devices matching one of the supplied types + */ + private List<DeviceInfo> getConnectedDevicesOfTypes(Set<Integer> internalTypes) { + ArrayList<DeviceInfo> devices = new ArrayList<>(); synchronized (mDevicesLock) { for (DeviceInfo di : mConnectedDevices.values()) { - if (di.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) { - return true; + if (internalTypes.contains(di.mDeviceType)) { + devices.add(di); } } - return false; } + return devices; + } + + /* package */ AudioDeviceAttributes getDeviceOfType(int type) { + DeviceInfo di = getFirstConnectedDeviceOfTypes(Sets.newHashSet(type)); + return di == null ? null : new AudioDeviceAttributes( + di.mDeviceType, di.mDeviceAddress, di.mDeviceName); } @GuardedBy("mDevicesLock") - private void makeLeAudioDeviceAvailable(String address, String name, int streamType, - int volumeIndex, int device, String eventSource) { + private void makeLeAudioDeviceAvailable( + AudioDeviceBroker.BtDeviceInfo btInfo, int streamType, String eventSource) { + final String address = btInfo.mDevice.getAddress(); + final String name = BtHelper.getName(btInfo.mDevice); + final int volumeIndex = btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10; + final int device = btInfo.mAudioSystemDevice; + if (device != AudioSystem.DEVICE_NONE) { /* Audio Policy sees Le Audio similar to A2DP. Let's make sure * AUDIO_POLICY_FORCE_NO_BT_A2DP is not set */ mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource); - final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( - device, address, name), - AudioSystem.DEVICE_STATE_AVAILABLE, - AudioSystem.AUDIO_FORMAT_DEFAULT); + AudioDeviceAttributes ada = new AudioDeviceAttributes(device, address, name); + final int res = AudioSystem.setDeviceConnectionState(ada, + AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); if (res != AudioSystem.AUDIO_STATUS_OK) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( "APM failed to make available LE Audio device addr=" + address @@ -1624,12 +1897,13 @@ public class AudioDeviceInventory { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( "LE Audio device addr=" + address + " now available").printLog(TAG)); } - // Reset LEA suspend state each time a new sink is connected mDeviceBroker.clearLeAudioSuspended(); + UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada); mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address), - new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); + new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT, + sensorUuid)); mDeviceBroker.postAccessoryPlugMediaUnmute(device); setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false); } @@ -1645,6 +1919,8 @@ public class AudioDeviceInventory { final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType); mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType); mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable"); + + updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/); } @GuardedBy("mDevicesLock") @@ -1669,6 +1945,7 @@ public class AudioDeviceInventory { } setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/); + updateBluetoothPreferredModes_l(null /*connectedDevice*/); purgeDevicesRoles_l(); } @@ -2005,18 +2282,6 @@ public class AudioDeviceInventory { } } - /* package */ AudioDeviceAttributes getDeviceOfType(int type) { - synchronized (mDevicesLock) { - for (DeviceInfo di : mConnectedDevices.values()) { - if (di.mDeviceType == type) { - return new AudioDeviceAttributes( - di.mDeviceType, di.mDeviceAddress, di.mDeviceName); - } - } - } - return null; - } - //---------------------------------------------------------- // For tests only @@ -2027,10 +2292,12 @@ public class AudioDeviceInventory { */ @VisibleForTesting public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) { - final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - device.getAddress()); - synchronized (mDevicesLock) { - return (mConnectedDevices.get(key) != null); + for (DeviceInfo di : getConnectedDevicesOfTypes( + Sets.newHashSet(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) { + if (di.mDeviceAddress.equals(device.getAddress())) { + return true; + } } + return false; } } diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 8c27c3ecfd87..e46c3cc4a285 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -33,6 +33,7 @@ import android.media.AudioManager; import android.media.AudioSystem; import android.media.BluetoothProfileConnectionInfo; import android.os.Binder; +import android.os.Bundle; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; @@ -150,60 +151,12 @@ public class BtHelper { } } - //---------------------------------------------------------------------- - /*package*/ static class BluetoothA2dpDeviceInfo { - private final @NonNull BluetoothDevice mBtDevice; - private final int mVolume; - private final @AudioSystem.AudioFormatNativeEnumForBtCodec int mCodec; - - BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice) { - this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT); - } - - BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice, int volume, int codec) { - mBtDevice = btDevice; - mVolume = volume; - mCodec = codec; - } - - public @NonNull BluetoothDevice getBtDevice() { - return mBtDevice; - } - - public int getVolume() { - return mVolume; - } - - public @AudioSystem.AudioFormatNativeEnumForBtCodec int getCodec() { - return mCodec; - } - - // redefine equality op so we can match messages intended for this device - @Override - public boolean equals(Object o) { - if (o == null) { - return false; - } - if (this == o) { - return true; - } - if (o instanceof BluetoothA2dpDeviceInfo) { - return mBtDevice.equals(((BluetoothA2dpDeviceInfo) o).getBtDevice()); - } - return false; - } - - - } - // A2DP device events /*package*/ static final int EVENT_DEVICE_CONFIG_CHANGE = 0; - /*package*/ static final int EVENT_ACTIVE_DEVICE_CHANGE = 1; - /*package*/ static String a2dpDeviceEventToString(int event) { + /*package*/ static String deviceEventToString(int event) { switch (event) { case EVENT_DEVICE_CONFIG_CHANGE: return "DEVICE_CONFIG_CHANGE"; - case EVENT_ACTIVE_DEVICE_CHANGE: return "ACTIVE_DEVICE_CHANGE"; default: return new String("invalid event:" + event); } @@ -620,11 +573,12 @@ public class BtHelper { return btHeadsetDeviceToAudioDevice(mBluetoothHeadsetDevice); } - private AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) { + private static AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) { if (btDevice == null) { return new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""); } String address = btDevice.getAddress(); + String name = getName(btDevice); if (!BluetoothAdapter.checkBluetoothAddress(address)) { address = ""; } @@ -646,7 +600,7 @@ public class BtHelper { + " btClass: " + (btClass == null ? "Unknown" : btClass) + " nativeType: " + nativeType + " address: " + address); } - return new AudioDeviceAttributes(nativeType, address); + return new AudioDeviceAttributes(nativeType, address, name); } private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) { @@ -655,12 +609,9 @@ public class BtHelper { } int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; AudioDeviceAttributes audioDevice = btHeadsetDeviceToAudioDevice(btDevice); - String btDeviceName = getName(btDevice); boolean result = false; if (isActive) { - result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( - audioDevice.getInternalType(), audioDevice.getAddress(), btDeviceName), - isActive); + result |= mDeviceBroker.handleDeviceConnection(audioDevice, isActive, btDevice); } else { int[] outDeviceTypes = { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, @@ -669,14 +620,14 @@ public class BtHelper { }; for (int outDeviceType : outDeviceTypes) { result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( - outDeviceType, audioDevice.getAddress(), btDeviceName), - isActive); + outDeviceType, audioDevice.getAddress(), audioDevice.getName()), + isActive, btDevice); } } // handleDeviceConnection() && result to make sure the method get executed result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( - inDevice, audioDevice.getAddress(), btDeviceName), - isActive) && result; + inDevice, audioDevice.getAddress(), audioDevice.getName()), + isActive, btDevice) && result; return result; } @@ -973,6 +924,30 @@ public class BtHelper { } } + /*package */ static int getProfileFromType(int deviceType) { + if (AudioSystem.isBluetoothA2dpOutDevice(deviceType)) { + return BluetoothProfile.A2DP; + } else if (AudioSystem.isBluetoothScoDevice(deviceType)) { + return BluetoothProfile.HEADSET; + } else if (AudioSystem.isBluetoothLeDevice(deviceType)) { + return BluetoothProfile.LE_AUDIO; + } + return 0; // 0 is not a valid profile + } + + /*package */ static Bundle getPreferredAudioProfiles(String address) { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + return adapter.getPreferredAudioProfiles(adapter.getRemoteDevice(address)); + } + + /** + * Notifies Bluetooth framework that new preferred audio profiles for Bluetooth devices + * have been applied. + */ + public static void onNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice) { + BluetoothAdapter.getDefaultAdapter().notifyActiveDeviceChangeApplied(btDevice); + } + /** * Returns the string equivalent for the btDeviceClass class. */ |