Merge "LeAudio: Fix audio modes priority" into tm-qpr-dev
diff --git a/android/app/src/com/android/bluetooth/a2dp/A2dpService.java b/android/app/src/com/android/bluetooth/a2dp/A2dpService.java
index 03662a0..cffc4ed6 100644
--- a/android/app/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/android/app/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -494,13 +494,32 @@
previousActiveDevice = mActiveDevice;
}
+ int prevActiveConnectionState = getConnectionState(previousActiveDevice);
+
+ // As per b/202602952, if we remove the active device due to a disconnection,
+ // we need to check if another device is connected and set it active instead.
+ // Calling this before any other active related calls has the same effect as
+ // a classic active device switch.
+ BluetoothDevice fallbackdevice = getFallbackDevice();
+ if (fallbackdevice != null && prevActiveConnectionState
+ != BluetoothProfile.STATE_CONNECTED) {
+ setActiveDevice(fallbackdevice);
+ return;
+ }
+
// This needs to happen before we inform the audio manager that the device
// disconnected. Please see comment in updateAndBroadcastActiveDevice() for why.
updateAndBroadcastActiveDevice(null);
- // Make sure the Audio Manager knows the previous Active device is removed.
+ // Make sure the Audio Manager knows the previous Active device is disconnected.
+ // However, if A2DP is still connected and not forcing stop audio for that remote
+ // device, the user has explicitly switched the output to the local device and music
+ // should continue playing. Otherwise, the remote device has been indeed disconnected
+ // and audio should be suspended before switching the output to the local device.
+ boolean stopAudio = forceStopPlayingAudio || (prevActiveConnectionState
+ != BluetoothProfile.STATE_CONNECTED);
mAudioManager.handleBluetoothActiveDeviceChanged(null, previousActiveDevice,
- BluetoothProfileConnectionInfo.createA2dpInfo(!forceStopPlayingAudio, -1));
+ BluetoothProfileConnectionInfo.createA2dpInfo(!stopAudio, -1));
synchronized (mStateMachines) {
// Make sure the Active device in native layer is set to null and audio is off
@@ -544,22 +563,10 @@
* @return true on success, otherwise false
*/
public boolean setActiveDevice(BluetoothDevice device) {
- return setActiveDevice(device, false);
- }
-
- /**
- * Set the active device.
- *
- * @param device the active device
- * @param hasFallbackDevice whether it has fallback device when the {@code device}
- * is {@code null}.
- * @return true on success, otherwise false
- */
- public boolean setActiveDevice(BluetoothDevice device, boolean hasFallbackDevice) {
synchronized (mActiveSwitchingGuard) {
if (device == null) {
// Remove active device and continue playing audio only if necessary.
- removeActiveDevice(!hasFallbackDevice);
+ removeActiveDevice(false);
return true;
}
@@ -1246,9 +1253,10 @@
if (toState == BluetoothProfile.STATE_CONNECTED && (mMaxConnectedAudioDevices == 1)) {
setActiveDevice(device);
}
-
- // When disconnected, ActiveDeviceManager will call setActiveDevice(null)
-
+ // Check if the active device is not connected anymore
+ if (isActiveDevice(device) && (fromState == BluetoothProfile.STATE_CONNECTED)) {
+ setActiveDevice(null);
+ }
// Check if the device is disconnected - if unbond, remove the state machine
if (toState == BluetoothProfile.STATE_DISCONNECTED) {
if (mAdapterService.getBondState(device) == BluetoothDevice.BOND_NONE) {
diff --git a/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java b/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
index 1d2666e..f043394 100644
--- a/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
+++ b/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
@@ -49,7 +49,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
-import java.util.concurrent.RejectedExecutionException;
/**
* The active device manager is responsible for keeping track of the
@@ -125,32 +124,24 @@
private static final int MESSAGE_HAP_ACTION_CONNECTION_STATE_CHANGED = 10;
private static final int MESSAGE_HAP_ACTION_ACTIVE_DEVICE_CHANGED = 11;
- // Used when it is needed to find a fallback device
- private static final int PROFILE_NOT_DECIDED_YET = -1;
- // Used for built-in audio device
- private static final int PROFILE_USE_BUILTIN_AUDIO_DEVICE = 0;
-
private final AdapterService mAdapterService;
private final ServiceFactory mFactory;
private HandlerThread mHandlerThread = null;
private Handler mHandler = null;
private final AudioManager mAudioManager;
private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback;
- private final AudioManagerOnModeChangedListener mAudioManagerOnModeChangedListener;
private final List<BluetoothDevice> mA2dpConnectedDevices = new ArrayList<>();
private final List<BluetoothDevice> mHfpConnectedDevices = new ArrayList<>();
private final List<BluetoothDevice> mHearingAidConnectedDevices = new ArrayList<>();
private final List<BluetoothDevice> mLeAudioConnectedDevices = new ArrayList<>();
private final List<BluetoothDevice> mLeHearingAidConnectedDevices = new ArrayList<>();
- private final List<BluetoothDevice> mPendingLeHearingAidActiveDevice = new ArrayList<>();
+ private List<BluetoothDevice> mPendingLeHearingAidActiveDevice = new ArrayList<>();
private BluetoothDevice mA2dpActiveDevice = null;
private BluetoothDevice mHfpActiveDevice = null;
private BluetoothDevice mHearingAidActiveDevice = null;
private BluetoothDevice mLeAudioActiveDevice = null;
private BluetoothDevice mLeHearingAidActiveDevice = null;
- private int mActiveMediaProfile = PROFILE_USE_BUILTIN_AUDIO_DEVICE;
- private int mActiveCallProfile = PROFILE_USE_BUILTIN_AUDIO_DEVICE;
// Broadcast receiver for all changes
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -220,7 +211,6 @@
@Override
public void handleMessage(Message msg) {
- boolean isMediaMode = isMediaMode(mAudioManager.getMode());
switch (msg.what) {
case MESSAGE_ADAPTER_ACTION_STATE_CHANGED: {
Intent intent = (Intent) msg.obj;
@@ -255,24 +245,16 @@
if (mA2dpConnectedDevices.contains(device)) {
break; // The device is already connected
}
- // New connected A2DP device
mA2dpConnectedDevices.add(device);
- if (mActiveMediaProfile != BluetoothProfile.HEARING_AID
- && mActiveMediaProfile != BluetoothProfile.HAP_CLIENT) {
- if (isMediaMode || mActiveCallProfile == BluetoothProfile.HEADSET) {
- // select the device as active if not lazy active
- setA2dpActiveDevice(device);
- setLeAudioActiveDevice(null, true);
- mActiveMediaProfile = BluetoothProfile.A2DP;
- } else {
- // Lazy active A2DP if it is not being used.
- mActiveMediaProfile = PROFILE_NOT_DECIDED_YET;
- }
+ if (mHearingAidActiveDevice == null && mLeHearingAidActiveDevice == null) {
+ // New connected device: select it as active
+ setA2dpActiveDevice(device);
+ setLeAudioActiveDevice(null);
}
break;
}
if (prevState == BluetoothProfile.STATE_CONNECTED) {
- // A2DP device disconnected
+ // Device disconnected
if (DBG) {
Log.d(TAG,
"handleMessage(MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED): "
@@ -280,15 +262,10 @@
}
mA2dpConnectedDevices.remove(device);
if (Objects.equals(mA2dpActiveDevice, device)) {
- mActiveMediaProfile = PROFILE_NOT_DECIDED_YET;
- if (isMediaMode) {
- if (!setFallbackDeviceActive()) {
- mActiveMediaProfile = PROFILE_USE_BUILTIN_AUDIO_DEVICE;
- setA2dpActiveDevice(null);
- }
- } else {
+ if (mA2dpConnectedDevices.isEmpty()) {
setA2dpActiveDevice(null);
}
+ setFallbackDeviceActive();
}
}
}
@@ -302,12 +279,12 @@
Log.d(TAG, "handleMessage(MESSAGE_A2DP_ACTION_ACTIVE_DEVICE_CHANGED): "
+ "device= " + device);
}
- if (device != null) {
- mActiveMediaProfile = BluetoothProfile.A2DP;
- if (!Objects.equals(mA2dpActiveDevice, device)) {
- setHearingAidActiveDevice(null, true);
- setLeAudioActiveDevice(null, true);
- }
+ if (device != null && !Objects.equals(mA2dpActiveDevice, device)) {
+ setHearingAidActiveDevice(null);
+ setLeAudioActiveDevice(null);
+ }
+ if (mHfpConnectedDevices.contains(device)) {
+ setHfpActiveDevice(device);
}
// Just assign locally the new value
mA2dpActiveDevice = device;
@@ -334,24 +311,16 @@
if (mHfpConnectedDevices.contains(device)) {
break; // The device is already connected
}
- // New connected HFP device.
mHfpConnectedDevices.add(device);
- if (mActiveCallProfile != BluetoothProfile.HEARING_AID
- && mActiveCallProfile != BluetoothProfile.HAP_CLIENT) {
- if (!isMediaMode || mActiveMediaProfile == BluetoothProfile.A2DP) {
- // select the device as active if not lazy active
- setHfpActiveDevice(device);
- setLeAudioActiveDevice(null);
- mActiveCallProfile = BluetoothProfile.HEADSET;
- } else {
- // Lazy active HFP if it is not being used.
- mActiveCallProfile = PROFILE_NOT_DECIDED_YET;
- }
+ if (mHearingAidActiveDevice == null && mLeHearingAidActiveDevice == null) {
+ // New connected device: select it as active
+ setHfpActiveDevice(device);
+ setLeAudioActiveDevice(null);
}
break;
}
if (prevState == BluetoothProfile.STATE_CONNECTED) {
- // HFP device disconnected
+ // Device disconnected
if (DBG) {
Log.d(TAG,
"handleMessage(MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED): "
@@ -359,15 +328,10 @@
}
mHfpConnectedDevices.remove(device);
if (Objects.equals(mHfpActiveDevice, device)) {
- mActiveCallProfile = PROFILE_NOT_DECIDED_YET;
- if (!isMediaMode) {
- if (!setFallbackDeviceActive()) {
- mActiveCallProfile = PROFILE_USE_BUILTIN_AUDIO_DEVICE;
- setHfpActiveDevice(null);
- }
- } else {
+ if (mHfpConnectedDevices.isEmpty()) {
setHfpActiveDevice(null);
}
+ setFallbackDeviceActive();
}
}
}
@@ -381,12 +345,12 @@
Log.d(TAG, "handleMessage(MESSAGE_HFP_ACTION_ACTIVE_DEVICE_CHANGED): "
+ "device= " + device);
}
- if (device != null) {
- mActiveCallProfile = BluetoothProfile.HEADSET;
- if (!Objects.equals(mHfpActiveDevice, device)) {
- setHearingAidActiveDevice(null, true);
- setLeAudioActiveDevice(null, true);
- }
+ if (device != null && !Objects.equals(mHfpActiveDevice, device)) {
+ setHearingAidActiveDevice(null);
+ setLeAudioActiveDevice(null);
+ }
+ if (mA2dpConnectedDevices.contains(device)) {
+ setA2dpActiveDevice(device);
}
// Just assign locally the new value
mHfpActiveDevice = device;
@@ -413,38 +377,25 @@
break; // The device is already connected
}
mHearingAidConnectedDevices.add(device);
- // New connected hearing aid device: select it as active
- mActiveMediaProfile = BluetoothProfile.HEARING_AID;
- mActiveCallProfile = BluetoothProfile.HEARING_AID;
+ // New connected device: select it as active
setHearingAidActiveDevice(device);
- setA2dpActiveDevice(null, true);
+ setA2dpActiveDevice(null);
setHfpActiveDevice(null);
- setLeAudioActiveDevice(null, true);
+ setLeAudioActiveDevice(null);
break;
}
if (prevState == BluetoothProfile.STATE_CONNECTED) {
- // Hearing aid device disconnected
+ // Device disconnected
if (DBG) {
Log.d(TAG, "handleMessage(MESSAGE_HEARING_AID_ACTION_CONNECTION_STATE"
+ "_CHANGED): device " + device + " disconnected");
}
mHearingAidConnectedDevices.remove(device);
if (Objects.equals(mHearingAidActiveDevice, device)) {
- if (mActiveMediaProfile == BluetoothProfile.HEARING_AID) {
- mActiveMediaProfile = PROFILE_NOT_DECIDED_YET;
- }
- if (mActiveCallProfile == BluetoothProfile.HEARING_AID) {
- mActiveCallProfile = PROFILE_NOT_DECIDED_YET;
- }
- if (!setFallbackDeviceActive()) {
+ if (mHearingAidConnectedDevices.isEmpty()) {
setHearingAidActiveDevice(null);
- if (mActiveMediaProfile == PROFILE_NOT_DECIDED_YET) {
- mActiveMediaProfile = PROFILE_USE_BUILTIN_AUDIO_DEVICE;
- }
- if (mActiveCallProfile == PROFILE_NOT_DECIDED_YET) {
- mActiveCallProfile = PROFILE_USE_BUILTIN_AUDIO_DEVICE;
- }
}
+ setFallbackDeviceActive();
}
}
}
@@ -458,18 +409,13 @@
Log.d(TAG, "handleMessage(MESSAGE_HA_ACTION_ACTIVE_DEVICE_CHANGED): "
+ "device= " + device);
}
- if (device != null && !Objects.equals(mHearingAidActiveDevice, device)) {
- setA2dpActiveDevice(null, true);
- setHfpActiveDevice(null);
- setLeAudioActiveDevice(null, true);
- if (isMediaMode) {
- mActiveMediaProfile = BluetoothProfile.HEARING_AID;
- } else {
- mActiveCallProfile = BluetoothProfile.HEARING_AID;
- }
- }
// Just assign locally the new value
mHearingAidActiveDevice = device;
+ if (device != null) {
+ setA2dpActiveDevice(null);
+ setHfpActiveDevice(null);
+ setLeAudioActiveDevice(null);
+ }
}
break;
@@ -483,80 +429,43 @@
// Nothing has changed
break;
}
- final LeAudioService leAudioService = mFactory.getLeAudioService();
-
if (nextState == BluetoothProfile.STATE_CONNECTED) {
// Device connected
if (DBG) {
Log.d(TAG, "handleMessage(MESSAGE_LE_AUDIO_ACTION_CONNECTION_STATE"
+ "_CHANGED): device " + device + " connected");
}
- if (leAudioService != null && device != null) {
- leAudioService.deviceConnected(device);
- }
if (mLeAudioConnectedDevices.contains(device)) {
break; // The device is already connected
}
mLeAudioConnectedDevices.add(device);
- if (mPendingLeHearingAidActiveDevice.contains(device)) {
- // LE hearing aid connected
- mActiveMediaProfile = BluetoothProfile.HAP_CLIENT;
- mActiveCallProfile = BluetoothProfile.HAP_CLIENT;
- setLeHearingAidActiveDevice(device);
- setHearingAidActiveDevice(null, true);
- setA2dpActiveDevice(null, true);
+ if (mHearingAidActiveDevice == null && mLeHearingAidActiveDevice == null
+ && mPendingLeHearingAidActiveDevice.isEmpty()) {
+ // New connected device: select it as active
+ setLeAudioActiveDevice(device);
+ setA2dpActiveDevice(null);
setHfpActiveDevice(null);
- } else {
- boolean setLeAudioActive = false;
- if (mActiveMediaProfile != BluetoothProfile.HEARING_AID
- && mActiveMediaProfile != BluetoothProfile.HAP_CLIENT) {
- mActiveMediaProfile = BluetoothProfile.LE_AUDIO;
- setLeAudioActive |= isMediaMode;
- }
- if (mActiveCallProfile != BluetoothProfile.HEARING_AID
- && mActiveCallProfile != BluetoothProfile.HAP_CLIENT) {
- mActiveCallProfile = BluetoothProfile.LE_AUDIO;
- setLeAudioActive |= !isMediaMode;
- }
- if (setLeAudioActive) {
- setLeAudioActiveDevice(device);
- setA2dpActiveDevice(null, true);
- setHfpActiveDevice(null);
- setHearingAidActiveDevice(null, true);
- }
+ } else if (mPendingLeHearingAidActiveDevice.contains(device)) {
+ setLeHearingAidActiveDevice(device);
+ setHearingAidActiveDevice(null);
+ setA2dpActiveDevice(null);
+ setHfpActiveDevice(null);
}
break;
}
if (prevState == BluetoothProfile.STATE_CONNECTED) {
- // LE audio device disconnected
+ // Device disconnected
if (DBG) {
Log.d(TAG, "handleMessage(MESSAGE_LE_AUDIO_ACTION_CONNECTION_STATE"
+ "_CHANGED): device " + device + " disconnected");
}
mLeAudioConnectedDevices.remove(device);
mLeHearingAidConnectedDevices.remove(device);
- boolean hasFallbackDevice = false;
if (Objects.equals(mLeAudioActiveDevice, device)) {
- if (mActiveMediaProfile == BluetoothProfile.LE_AUDIO
- || mActiveMediaProfile == BluetoothProfile.HAP_CLIENT) {
- mActiveMediaProfile = PROFILE_NOT_DECIDED_YET;
- }
- if (mActiveCallProfile == BluetoothProfile.LE_AUDIO
- || mActiveCallProfile == BluetoothProfile.HAP_CLIENT) {
- mActiveCallProfile = PROFILE_NOT_DECIDED_YET;
- }
- if (!setFallbackDeviceActive()) {
+ if (mLeAudioConnectedDevices.isEmpty()) {
setLeAudioActiveDevice(null);
- if (mActiveMediaProfile == PROFILE_NOT_DECIDED_YET) {
- mActiveMediaProfile = PROFILE_USE_BUILTIN_AUDIO_DEVICE;
- }
- if (mActiveCallProfile == PROFILE_NOT_DECIDED_YET) {
- mActiveCallProfile = PROFILE_USE_BUILTIN_AUDIO_DEVICE;
- }
}
- }
- if (leAudioService != null && device != null) {
- leAudioService.deviceDisconnected(device, hasFallbackDevice);
+ setFallbackDeviceActive();
}
}
}
@@ -573,20 +482,12 @@
Log.d(TAG, "handleMessage(MESSAGE_LE_AUDIO_ACTION_ACTIVE_DEVICE_CHANGED): "
+ "device= " + device);
}
-
- if (device != null && !Objects.equals(mLeAudioActiveDevice, device)) {
- setA2dpActiveDevice(null, true);
- setHfpActiveDevice(null);
- setHearingAidActiveDevice(null, true);
- int profile = mLeHearingAidConnectedDevices.contains(device)
- ? BluetoothProfile.HAP_CLIENT : BluetoothProfile.LE_AUDIO;
- if (isMediaMode) {
- mActiveMediaProfile = profile;
- } else {
- mActiveCallProfile = profile;
- }
- }
// Just assign locally the new value
+ if (device != null && !Objects.equals(mLeAudioActiveDevice, device)) {
+ setA2dpActiveDevice(null);
+ setHfpActiveDevice(null);
+ setHearingAidActiveDevice(null);
+ }
mLeAudioActiveDevice = device;
}
break;
@@ -613,22 +514,19 @@
mLeHearingAidConnectedDevices.add(device);
if (!mLeAudioConnectedDevices.contains(device)) {
mPendingLeHearingAidActiveDevice.add(device);
+ } else if (Objects.equals(mLeAudioActiveDevice, device)) {
+ mLeHearingAidActiveDevice = device;
} else {
- mActiveMediaProfile = BluetoothProfile.HAP_CLIENT;
- mActiveCallProfile = BluetoothProfile.HAP_CLIENT;
- if (Objects.equals(mLeAudioActiveDevice, device)) {
- mLeHearingAidActiveDevice = device;
- } else {
- setLeHearingAidActiveDevice(device);
- setHearingAidActiveDevice(null, true);
- setA2dpActiveDevice(null, true);
- setHfpActiveDevice(null);
- }
+ // New connected device: select it as active
+ setLeHearingAidActiveDevice(device);
+ setHearingAidActiveDevice(null);
+ setA2dpActiveDevice(null);
+ setHfpActiveDevice(null);
}
break;
}
if (prevState == BluetoothProfile.STATE_CONNECTED) {
- // LE hearing aid device disconnected
+ // Device disconnected
if (DBG) {
Log.d(TAG, "handleMessage(MESSAGE_HAP_ACTION_CONNECTION_STATE"
+ "_CHANGED): device " + device + " disconnected");
@@ -655,14 +553,9 @@
}
// Just assign locally the new value
if (device != null && !Objects.equals(mLeHearingAidActiveDevice, device)) {
- if (isMediaMode) {
- mActiveMediaProfile = BluetoothProfile.HAP_CLIENT;
- } else {
- mActiveCallProfile = BluetoothProfile.HAP_CLIENT;
- }
- setHearingAidActiveDevice(null, true);
- setA2dpActiveDevice(null, true);
+ setA2dpActiveDevice(null);
setHfpActiveDevice(null);
+ setHearingAidActiveDevice(null);
}
mLeHearingAidActiveDevice = mLeAudioActiveDevice = device;
}
@@ -671,167 +564,6 @@
}
}
- private class AudioManagerOnModeChangedListener implements AudioManager.OnModeChangedListener {
- public void onModeChanged(int mode) {
- if (isMediaMode(mode)) {
- setMediaProfileActive();
- } else {
- setCallProfileActive();
- }
- }
-
- private void setMediaProfileActive() {
- BluetoothDevice device = null;
- switch (mActiveMediaProfile) {
- case PROFILE_NOT_DECIDED_YET: {
- if (!setFallbackDeviceActive()) {
- mActiveMediaProfile = PROFILE_USE_BUILTIN_AUDIO_DEVICE;
- }
- break;
- }
-
- case BluetoothProfile.A2DP: {
- if (mA2dpActiveDevice == null) {
- A2dpService a2dpService = mFactory.getA2dpService();
- if (a2dpService != null) {
- device = a2dpService.getFallbackDevice();
- }
- if (device != null) {
- setA2dpActiveDevice(device);
- setHearingAidActiveDevice(null, true);
- setLeAudioActiveDevice(null, true);
- } else {
- mActiveMediaProfile = PROFILE_USE_BUILTIN_AUDIO_DEVICE;
- }
- }
- break;
- }
-
- case BluetoothProfile.HEARING_AID: {
- if (mHearingAidActiveDevice == null) {
- DatabaseManager dbManager = mAdapterService.getDatabase();
- if (dbManager != null) {
- device = dbManager.getMostRecentlyConnectedDevicesInList(
- mHearingAidConnectedDevices);
- }
- if (device != null) {
- setHearingAidActiveDevice(device);
- setA2dpActiveDevice(null, true);
- setLeAudioActiveDevice(null, true);
- } else {
- mActiveMediaProfile = PROFILE_USE_BUILTIN_AUDIO_DEVICE;
- }
- }
- break;
- }
-
- case BluetoothProfile.LE_AUDIO: {
- if (mLeAudioActiveDevice == null) {
- DatabaseManager dbManager = mAdapterService.getDatabase();
- if (dbManager != null) {
- device = dbManager.getMostRecentlyConnectedDevicesInList(
- mLeAudioConnectedDevices);
- }
- if (device != null) {
- setLeAudioActiveDevice(device);
- setA2dpActiveDevice(null, true);
- setHfpActiveDevice(null);
- setHearingAidActiveDevice(null, true);
- } else {
- mActiveMediaProfile = PROFILE_USE_BUILTIN_AUDIO_DEVICE;
- }
- }
- break;
- }
-
- case BluetoothProfile.HAP_CLIENT: {
- if (mLeHearingAidActiveDevice == null) {
- DatabaseManager dbManager = mAdapterService.getDatabase();
- if (dbManager != null) {
- device = dbManager.getMostRecentlyConnectedDevicesInList(
- mLeHearingAidConnectedDevices);
- }
- if (device != null) {
- setLeHearingAidActiveDevice(device);
- setA2dpActiveDevice(null, true);
- setHfpActiveDevice(null);
- setHearingAidActiveDevice(null, true);
- } else {
- mActiveMediaProfile = PROFILE_USE_BUILTIN_AUDIO_DEVICE;
- }
- }
- break;
- }
- }
- }
-
- private void setCallProfileActive() {
- BluetoothDevice device = null;
- switch (mActiveCallProfile) {
- case PROFILE_NOT_DECIDED_YET: {
- if (!setFallbackDeviceActive()) {
- mActiveCallProfile = PROFILE_USE_BUILTIN_AUDIO_DEVICE;
- }
- break;
- }
-
- case BluetoothProfile.HEADSET: {
- if (mHfpActiveDevice == null) {
- HeadsetService headsetService = mFactory.getHeadsetService();
- if (headsetService != null) {
- device = headsetService.getFallbackDevice();
- }
- if (device != null) {
- setHfpActiveDevice(device);
- setHearingAidActiveDevice(null, true);
- setLeAudioActiveDevice(null, true);
- } else {
- mActiveCallProfile = PROFILE_USE_BUILTIN_AUDIO_DEVICE;
- }
- }
- break;
- }
-
- case BluetoothProfile.HEARING_AID: {
- if (mHearingAidActiveDevice == null) {
- DatabaseManager dbManager = mAdapterService.getDatabase();
- if (dbManager != null) {
- device = dbManager.getMostRecentlyConnectedDevicesInList(
- mHearingAidConnectedDevices);
- }
- if (device != null) {
- setHearingAidActiveDevice(device);
- setHfpActiveDevice(null);
- setLeAudioActiveDevice(null);
- } else {
- mActiveCallProfile = PROFILE_USE_BUILTIN_AUDIO_DEVICE;
- }
- }
- break;
- }
-
- case BluetoothProfile.LE_AUDIO: {
- if (mLeAudioActiveDevice == null) {
- DatabaseManager dbManager = mAdapterService.getDatabase();
- if (dbManager != null) {
- device = dbManager.getMostRecentlyConnectedDevicesInList(
- mLeAudioConnectedDevices);
- }
- if (device != null) {
- setLeAudioActiveDevice(device);
- setA2dpActiveDevice(null, true);
- setHfpActiveDevice(null);
- setHearingAidActiveDevice(null, true);
- } else {
- mActiveCallProfile = PROFILE_USE_BUILTIN_AUDIO_DEVICE;
- }
- }
- break;
- }
- }
- }
- }
-
/** Notifications of audio device connection and disconnection events. */
@SuppressLint("AndroidFrameworkRequiresPermission")
private class AudioManagerAudioDeviceCallback extends AudioDeviceCallback {
@@ -878,7 +610,6 @@
mFactory = factory;
mAudioManager = service.getSystemService(AudioManager.class);
mAudioManagerAudioDeviceCallback = new AudioManagerAudioDeviceCallback();
- mAudioManagerOnModeChangedListener = new AudioManagerOnModeChangedListener();
}
void start() {
@@ -905,11 +636,6 @@
mAdapterService.registerReceiver(mReceiver, filter);
mAudioManager.registerAudioDeviceCallback(mAudioManagerAudioDeviceCallback, mHandler);
- mAudioManager.addOnModeChangedListener(command -> {
- if (!mHandler.post(command)) {
- throw new RejectedExecutionException(mHandler + " is shutting down");
- }
- }, mAudioManagerOnModeChangedListener);
}
void cleanup() {
@@ -918,7 +644,6 @@
}
mAudioManager.unregisterAudioDeviceCallback(mAudioManagerAudioDeviceCallback);
- mAudioManager.removeOnModeChangedListener(mAudioManagerOnModeChangedListener);
mAdapterService.unregisterReceiver(mReceiver);
if (mHandlerThread != null) {
mHandlerThread.quit();
@@ -942,10 +667,6 @@
}
private void setA2dpActiveDevice(BluetoothDevice device) {
- setA2dpActiveDevice(device, false);
- }
-
- private void setA2dpActiveDevice(BluetoothDevice device, boolean hasFallbackDevice) {
if (DBG) {
Log.d(TAG, "setA2dpActiveDevice(" + device + ")");
}
@@ -953,7 +674,7 @@
if (a2dpService == null) {
return;
}
- if (!a2dpService.setActiveDevice(device, hasFallbackDevice)) {
+ if (!a2dpService.setActiveDevice(device)) {
return;
}
mA2dpActiveDevice = device;
@@ -975,10 +696,6 @@
}
private void setHearingAidActiveDevice(BluetoothDevice device) {
- setHearingAidActiveDevice(device, false);
- }
-
- private void setHearingAidActiveDevice(BluetoothDevice device, boolean hasFallbackDevice) {
if (DBG) {
Log.d(TAG, "setHearingAidActiveDevice(" + device + ")");
}
@@ -986,16 +703,13 @@
if (hearingAidService == null) {
return;
}
- if (!hearingAidService.setActiveDevice(device, hasFallbackDevice)) {
+ if (!hearingAidService.setActiveDevice(device)) {
return;
}
mHearingAidActiveDevice = device;
}
- private void setLeAudioActiveDevice(BluetoothDevice device) {
- setLeAudioActiveDevice(device, false);
- }
- private void setLeAudioActiveDevice(BluetoothDevice device, boolean hasFallbackDevice) {
+ private void setLeAudioActiveDevice(BluetoothDevice device) {
if (DBG) {
Log.d(TAG, "setLeAudioActiveDevice(" + device + ")");
}
@@ -1003,7 +717,7 @@
if (leAudioService == null) {
return;
}
- if (!leAudioService.setActiveDevice(device, hasFallbackDevice)) {
+ if (!leAudioService.setActiveDevice(device)) {
return;
}
mLeAudioActiveDevice = device;
@@ -1014,9 +728,6 @@
}
private void setLeHearingAidActiveDevice(BluetoothDevice device) {
- if (DBG) {
- Log.d(TAG, "setLeHearingAidActiveDevice(" + device + ")");
- }
if (!Objects.equals(mLeAudioActiveDevice, device)) {
setLeAudioActiveDevice(device);
}
@@ -1027,32 +738,13 @@
}
}
- private boolean isMediaMode(int mode) {
- switch (mode) {
- case AudioManager.MODE_RINGTONE:
- final HeadsetService headsetService = mFactory.getHeadsetService();
- if (headsetService != null && headsetService.isInbandRingingEnabled()) {
- return false;
- }
- return true;
- case AudioManager.MODE_IN_CALL:
- case AudioManager.MODE_IN_COMMUNICATION:
- case AudioManager.MODE_CALL_SCREENING:
- case AudioManager.MODE_CALL_REDIRECT:
- case AudioManager.MODE_COMMUNICATION_REDIRECT:
- return false;
- default:
- return true;
- }
- }
-
- private boolean setFallbackDeviceActive() {
+ private void setFallbackDeviceActive() {
if (DBG) {
Log.d(TAG, "setFallbackDeviceActive");
}
DatabaseManager dbManager = mAdapterService.getDatabase();
if (dbManager == null) {
- return false;
+ return;
}
List<BluetoothDevice> connectedHearingAidDevices = new ArrayList<>();
if (!mHearingAidConnectedDevices.isEmpty()) {
@@ -1070,31 +762,19 @@
Log.d(TAG, "set hearing aid device active: " + device);
}
setHearingAidActiveDevice(device);
- setA2dpActiveDevice(null, true);
+ setA2dpActiveDevice(null);
setHfpActiveDevice(null);
- setLeAudioActiveDevice(null, true);
- if (mActiveMediaProfile == PROFILE_NOT_DECIDED_YET) {
- mActiveMediaProfile = BluetoothProfile.HEARING_AID;
- }
- if (mActiveCallProfile == PROFILE_NOT_DECIDED_YET) {
- mActiveCallProfile = BluetoothProfile.HEARING_AID;
- }
+ setLeAudioActiveDevice(null);
} else {
if (DBG) {
Log.d(TAG, "set LE hearing aid device active: " + device);
}
setLeHearingAidActiveDevice(device);
- setHearingAidActiveDevice(null, true);
- setA2dpActiveDevice(null, true);
+ setHearingAidActiveDevice(null);
+ setA2dpActiveDevice(null);
setHfpActiveDevice(null);
- if (mActiveMediaProfile == PROFILE_NOT_DECIDED_YET) {
- mActiveMediaProfile = BluetoothProfile.HAP_CLIENT;
- }
- if (mActiveCallProfile == PROFILE_NOT_DECIDED_YET) {
- mActiveCallProfile = BluetoothProfile.HAP_CLIENT;
- }
}
- return true;
+ return;
}
}
@@ -1112,35 +792,41 @@
List<BluetoothDevice> connectedDevices = new ArrayList<>();
connectedDevices.addAll(mLeAudioConnectedDevices);
- boolean isMediaMode = isMediaMode(mAudioManager.getMode());
- if (isMediaMode) {
- if (a2dpFallbackDevice != null) {
- connectedDevices.add(a2dpFallbackDevice);
- }
- } else {
- if (headsetFallbackDevice != null) {
- connectedDevices.add(headsetFallbackDevice);
- }
+ switch (mAudioManager.getMode()) {
+ case AudioManager.MODE_NORMAL:
+ if (a2dpFallbackDevice != null) {
+ connectedDevices.add(a2dpFallbackDevice);
+ }
+ break;
+ case AudioManager.MODE_RINGTONE:
+ if (headsetFallbackDevice != null && headsetService.isInbandRingingEnabled()) {
+ connectedDevices.add(headsetFallbackDevice);
+ }
+ break;
+ default:
+ if (headsetFallbackDevice != null) {
+ connectedDevices.add(headsetFallbackDevice);
+ }
}
-
BluetoothDevice device = dbManager.getMostRecentlyConnectedDevicesInList(connectedDevices);
if (device != null) {
- if (isMediaMode) {
+ if (mAudioManager.getMode() == AudioManager.MODE_NORMAL) {
if (Objects.equals(a2dpFallbackDevice, device)) {
if (DBG) {
Log.d(TAG, "set A2DP device active: " + device);
}
setA2dpActiveDevice(device);
- setLeAudioActiveDevice(null, true);
- mActiveMediaProfile = BluetoothProfile.A2DP;
+ if (headsetFallbackDevice != null) {
+ setHfpActiveDevice(device);
+ setLeAudioActiveDevice(null);
+ }
} else {
if (DBG) {
Log.d(TAG, "set LE audio device active: " + device);
}
setLeAudioActiveDevice(device);
- setA2dpActiveDevice(null, true);
+ setA2dpActiveDevice(null);
setHfpActiveDevice(null);
- mActiveMediaProfile = BluetoothProfile.LE_AUDIO;
}
} else {
if (Objects.equals(headsetFallbackDevice, device)) {
@@ -1148,21 +834,20 @@
Log.d(TAG, "set HFP device active: " + device);
}
setHfpActiveDevice(device);
- setLeAudioActiveDevice(null);
- mActiveCallProfile = BluetoothProfile.HEADSET;
+ if (a2dpFallbackDevice != null) {
+ setA2dpActiveDevice(a2dpFallbackDevice);
+ setLeAudioActiveDevice(null);
+ }
} else {
if (DBG) {
Log.d(TAG, "set LE audio device active: " + device);
}
setLeAudioActiveDevice(device);
- setA2dpActiveDevice(null, true);
+ setA2dpActiveDevice(null);
setHfpActiveDevice(null);
- mActiveCallProfile = BluetoothProfile.LE_AUDIO;
}
}
- return true;
}
- return false;
}
private void resetState() {
@@ -1218,8 +903,6 @@
if (DBG) {
Log.d(TAG, "wiredAudioDeviceConnected");
}
- mActiveMediaProfile = PROFILE_USE_BUILTIN_AUDIO_DEVICE;
- mActiveCallProfile = PROFILE_USE_BUILTIN_AUDIO_DEVICE;
setA2dpActiveDevice(null);
setHfpActiveDevice(null);
setHearingAidActiveDevice(null);
diff --git a/android/app/src/com/android/bluetooth/hearingaid/HearingAidService.java b/android/app/src/com/android/bluetooth/hearingaid/HearingAidService.java
index 78afdb7..419e3c5 100644
--- a/android/app/src/com/android/bluetooth/hearingaid/HearingAidService.java
+++ b/android/app/src/com/android/bluetooth/hearingaid/HearingAidService.java
@@ -585,24 +585,13 @@
* @return true on success, otherwise false
*/
public boolean setActiveDevice(BluetoothDevice device) {
- return setActiveDevice(device, false);
- }
-
- /**
- * Set the active device.
- * @param device the new active device
- * @param hasFallbackDevice whether it has fallback device when the {@code device}
- * is {@code null}.
- * @return true on success, otherwise false
- */
- public boolean setActiveDevice(BluetoothDevice device, boolean hasFallbackDevice) {
if (DBG) {
Log.d(TAG, "setActiveDevice:" + device);
}
synchronized (mStateMachines) {
if (device == null) {
if (mActiveDeviceHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
- reportActiveDevice(null, hasFallbackDevice);
+ reportActiveDevice(null);
mActiveDeviceHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID;
}
return true;
@@ -621,7 +610,7 @@
BluetoothHearingAid.HI_SYNC_ID_INVALID);
if (deviceHiSyncId != mActiveDeviceHiSyncId) {
mActiveDeviceHiSyncId = deviceHiSyncId;
- reportActiveDevice(device, false);
+ reportActiveDevice(device);
}
}
return true;
@@ -772,7 +761,7 @@
* Report the active device change to the active device manager and the media framework.
* @param device the new active device; or null if no active device
*/
- private void reportActiveDevice(BluetoothDevice device, boolean hasFallbackDevice) {
+ private void reportActiveDevice(BluetoothDevice device) {
if (DBG) {
Log.d(TAG, "reportActiveDevice(" + device + ")");
}
@@ -783,7 +772,14 @@
BluetoothProfile.HEARING_AID, mAdapterService.obfuscateAddress(device),
mAdapterService.getMetricId(device));
- boolean stopAudio = device == null && !hasFallbackDevice;
+ Intent intent = new Intent(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
+
+ boolean stopAudio = device == null
+ && (getConnectionState(mPreviousAudioDevice) != BluetoothProfile.STATE_CONNECTED);
if (DBG) {
Log.d(TAG, "Hearing Aid audio: " + mPreviousAudioDevice + " -> " + device
+ ". Stop audio: " + stopAudio);
@@ -899,7 +895,7 @@
}
}
if (fromState == BluetoothProfile.STATE_CONNECTED && getConnectedDevices().isEmpty()) {
- // When disconnected, ActiveDeviceManager will call setActiveDevice(null)
+ setActiveDevice(null);
long myHiSyncId = getHiSyncId(device);
mHiSyncIdConnectedMap.put(myHiSyncId, false);
}
diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
index deb917b..e3b6af4 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
@@ -164,6 +164,7 @@
private final Map<BluetoothDevice, Integer> mDeviceAudioLocationMap = new ConcurrentHashMap<>();
private BroadcastReceiver mBondStateChangedReceiver;
+ private BroadcastReceiver mConnectionStateChangedReceiver;
private BroadcastReceiver mMuteStateChangedReceiver;
private int mStoredRingerMode = -1;
private Handler mHandler = new Handler(Looper.getMainLooper());
@@ -234,6 +235,10 @@
mBondStateChangedReceiver = new BondStateChangedReceiver();
registerReceiver(mBondStateChangedReceiver, filter);
filter = new IntentFilter();
+ filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
+ mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver();
+ registerReceiver(mConnectionStateChangedReceiver, filter);
+ filter = new IntentFilter();
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
mMuteStateChangedReceiver = new MuteStateChangedReceiver();
registerReceiver(mMuteStateChangedReceiver, filter);
@@ -245,7 +250,7 @@
// Initialize Broadcast native interface
if ((mAdapterService.getSupportedProfilesBitMask()
- & (1 << BluetoothProfile.LE_AUDIO_BROADCAST)) != 0) {
+ & (1 << BluetoothProfile.LE_AUDIO_BROADCAST)) != 0) {
Log.i(TAG, "Init Le Audio broadcaster");
mBroadcastCallbacks = new RemoteCallbackList<IBluetoothLeBroadcastCallback>();
mLeAudioBroadcasterNativeInterface = Objects.requireNonNull(
@@ -338,6 +343,8 @@
// Unregister broadcast receivers
unregisterReceiver(mBondStateChangedReceiver);
mBondStateChangedReceiver = null;
+ unregisterReceiver(mConnectionStateChangedReceiver);
+ mConnectionStateChangedReceiver = null;
unregisterReceiver(mMuteStateChangedReceiver);
mMuteStateChangedReceiver = null;
@@ -818,6 +825,12 @@
if (!Objects.equals(device, previousInDevice)
|| (oldSupportedByDeviceInput != newSupportedByDeviceInput)) {
mActiveAudioInDevice = newSupportedByDeviceInput ? device : null;
+ if (DBG) {
+ Log.d(TAG, " handleBluetoothActiveDeviceChanged previousInDevice: "
+ + previousInDevice + ", mActiveAudioInDevice" + mActiveAudioInDevice
+ + " isLeOutput: false");
+ }
+
return true;
}
Log.d(TAG, "updateActiveInDevice: Nothing to do.");
@@ -870,6 +883,11 @@
if (!Objects.equals(device, previousOutDevice)
|| (oldSupportedByDeviceOutput != newSupportedByDeviceOutput)) {
mActiveAudioOutDevice = newSupportedByDeviceOutput ? device : null;
+ if (DBG) {
+ Log.d(TAG, " handleBluetoothActiveDeviceChanged previousOutDevice: "
+ + previousOutDevice + ", mActiveOutDevice: " + mActiveAudioOutDevice
+ + " isLeOutput: true");
+ }
return true;
}
Log.d(TAG, "updateActiveOutDevice: Nothing to do.");
@@ -929,12 +947,6 @@
*/
private boolean updateActiveDevices(Integer groupId, Integer oldSupportedAudioDirections,
Integer newSupportedAudioDirections, boolean isActive) {
- return updateActiveDevices(groupId, oldSupportedAudioDirections,
- newSupportedAudioDirections, isActive, false);
- }
-
- private boolean updateActiveDevices(Integer groupId, Integer oldSupportedAudioDirections,
- Integer newSupportedAudioDirections, boolean isActive, boolean hasFallbackDevice) {
BluetoothDevice device = null;
BluetoothDevice previousActiveOutDevice = mActiveAudioOutDevice;
BluetoothDevice previousActiveInDevice = mActiveAudioInDevice;
@@ -943,19 +955,19 @@
device = getFirstDeviceFromGroup(groupId);
}
- boolean isActiveOutDeviceChanged = updateActiveOutDevice(device, groupId,
+ boolean isNewActiveOutDevice = updateActiveOutDevice(device, groupId,
oldSupportedAudioDirections, newSupportedAudioDirections);
- boolean isActiveInDeviceChanged = updateActiveInDevice(device, groupId,
+ boolean isNewActiveInDevice = updateActiveInDevice(device, groupId,
oldSupportedAudioDirections, newSupportedAudioDirections);
if (DBG) {
- Log.d(TAG, " isActiveOutDeviceUpdated: " + isActiveOutDeviceChanged + ", "
- + mActiveAudioOutDevice + ", isActiveInDeviceUpdated: "
- + isActiveInDeviceChanged + ", " + mActiveAudioInDevice);
+ Log.d(TAG, " isNewActiveOutDevice: " + isNewActiveOutDevice + ", "
+ + mActiveAudioOutDevice + ", isNewActiveInDevice: " + isNewActiveInDevice
+ + ", " + mActiveAudioInDevice);
}
/* Active device changed, there is need to inform about new active LE Audio device */
- if (isActiveOutDeviceChanged || isActiveInDeviceChanged) {
+ if (isNewActiveOutDevice || isNewActiveInDevice) {
/* Register for new device connection/disconnection in Audio Manager */
if (mActiveAudioOutDevice != null || mActiveAudioInDevice != null) {
/* Register for any device connection in case if any of devices become connected */
@@ -968,14 +980,14 @@
}
}
- if (isActiveOutDeviceChanged) {
+ if (isNewActiveOutDevice) {
int volume = IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME;
if (mActiveAudioOutDevice != null) {
volume = getAudioDeviceGroupVolume(groupId);
}
- final boolean suppressNoisyIntent = hasFallbackDevice || (mActiveAudioOutDevice != null)
+ final boolean suppressNoisyIntent = (mActiveAudioOutDevice != null)
|| (getConnectionState(previousActiveOutDevice)
== BluetoothProfile.STATE_CONNECTED);
@@ -983,7 +995,7 @@
previousActiveOutDevice, getLeAudioOutputProfile(suppressNoisyIntent, volume));
}
- if (isActiveInDeviceChanged) {
+ if (isNewActiveInDevice) {
mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioInDevice,
previousActiveInDevice, BluetoothProfileConnectionInfo.createLeAudioInfo(false,
false));
@@ -996,10 +1008,6 @@
* Set the active device group.
*/
private void setActiveGroupWithDevice(BluetoothDevice device) {
- setActiveGroupWithDevice(device, false);
- }
-
- private void setActiveGroupWithDevice(BluetoothDevice device, boolean hasFallbackDevice) {
int groupId = LE_AUDIO_GROUP_ID_INVALID;
if (device != null) {
@@ -1030,7 +1038,7 @@
* However we would like to notify audio framework that LeAudio is not
* active anymore and does not want to get more audio data.
*/
- handleGroupTransitToInactive(currentlyActiveGroupId, hasFallbackDevice);
+ handleGroupTransitToInactive(currentlyActiveGroupId);
}
}
@@ -1041,21 +1049,9 @@
* @return true on success, otherwise false
*/
public boolean setActiveDevice(BluetoothDevice device) {
- return setActiveDevice(device, false);
- }
-
- /**
- * Set the active group represented by device.
- *
- * @param device the new active device
- * @param hasFallbackDevice whether it has fallback device when the {@code device}
- * is {@code null}.
- * @return true on success, otherwise false
- */
- public boolean setActiveDevice(BluetoothDevice device, boolean hasFallbackDevice) {
/* Clear active group */
if (device == null) {
- setActiveGroupWithDevice(device, hasFallbackDevice);
+ setActiveGroupWithDevice(device);
return true;
}
if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
@@ -1211,7 +1207,7 @@
}
}
- private void handleGroupTransitToInactive(int groupId, boolean hasFallbackDevice) {
+ private void handleGroupTransitToInactive(int groupId) {
synchronized (mGroupLock) {
LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId);
if (descriptor == null || !descriptor.mIsActive) {
@@ -1221,7 +1217,7 @@
descriptor.mIsActive = false;
updateActiveDevices(groupId, descriptor.mDirection, AUDIO_DIRECTION_NONE,
- descriptor.mIsActive, hasFallbackDevice);
+ descriptor.mIsActive);
/* Clear lost devices */
if (DBG) Log.d(TAG, "Clear for group: " + groupId);
clearLostDevicesWhileStreaming(descriptor);
@@ -1394,8 +1390,9 @@
LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId);
if (descriptor != null) {
if (descriptor.mIsActive) {
- descriptor.mIsActive = updateActiveDevices(
- groupId, descriptor.mDirection, direction, descriptor.mIsActive);
+ descriptor.mIsActive =
+ updateActiveDevices(groupId, descriptor.mDirection, direction,
+ descriptor.mIsActive);
if (!descriptor.mIsActive) {
notifyGroupStatusChanged(groupId, BluetoothLeAudio.GROUP_STATUS_INACTIVE);
}
@@ -1425,7 +1422,7 @@
break;
}
case LeAudioStackEvent.GROUP_STATUS_INACTIVE: {
- handleGroupTransitToInactive(groupId, false);
+ handleGroupTransitToInactive(groupId);
break;
}
case LeAudioStackEvent.GROUP_STATUS_TURNED_IDLE_DURING_CALL: {
@@ -1650,92 +1647,100 @@
return result;
}
- /**
- * Handles the connection of LE Audio device.
- *
- * @param device THe device that is connected.
- */
- public synchronized void deviceConnected(BluetoothDevice device) {
- int myGroupId = getGroupId(device);
- if (myGroupId == LE_AUDIO_GROUP_ID_INVALID
- || getConnectedPeerDevices(myGroupId).size() == 1) {
- // Log LE Audio connection event if we are the first device in a set
- // Or when the GroupId has not been found
- // MetricsLogger.logProfileConnectionEvent(
- // BluetoothMetricsProto.ProfileId.LE_AUDIO);
+ @VisibleForTesting
+ synchronized void connectionStateChanged(BluetoothDevice device, int fromState, int toState) {
+ if ((device == null) || (fromState == toState)) {
+ Log.e(TAG, "connectionStateChanged: unexpected invocation. device=" + device
+ + " fromState=" + fromState + " toState=" + toState);
+ return;
}
-
- LeAudioGroupDescriptor descriptor = getGroupDescriptor(myGroupId);
- if (descriptor != null) {
- descriptor.mIsConnected = true;
- } else {
- Log.e(TAG, "no descriptors for group: " + myGroupId);
- }
-
- McpService mcpService = mServiceFactory.getMcpService();
- if (mcpService != null) {
- mcpService.setDeviceAuthorized(device, true);
- }
- }
-
- /**
- * Handle the disconnection of LE Audio device.
- *
- * @param device The device that is disconnected
- * @param hasFallbackDevice whether it has fallback device when the {@code device}
- * is {@code null}.
- */
- public synchronized void deviceDisconnected(BluetoothDevice device, boolean hasFallbackDevice) {
- // If unbond, remove the state machine
- int bondState = mAdapterService.getBondState(device);
- if (bondState == BluetoothDevice.BOND_NONE) {
- if (DBG) {
- Log.d(TAG, device + " is unbond. Remove state machine");
+ if (toState == BluetoothProfile.STATE_CONNECTED) {
+ int myGroupId = getGroupId(device);
+ if (myGroupId == LE_AUDIO_GROUP_ID_INVALID
+ || getConnectedPeerDevices(myGroupId).size() == 1) {
+ // Log LE Audio connection event if we are the first device in a set
+ // Or when the GroupId has not been found
+ // MetricsLogger.logProfileConnectionEvent(
+ // BluetoothMetricsProto.ProfileId.LE_AUDIO);
}
- removeStateMachine(device);
- }
- McpService mcpService = mServiceFactory.getMcpService();
- if (mcpService != null) {
- mcpService.setDeviceAuthorized(device, false);
- }
+ LeAudioGroupDescriptor descriptor = getGroupDescriptor(myGroupId);
+ if (descriptor != null) {
+ descriptor.mIsConnected = true;
+ } else {
+ Log.e(TAG, "no descriptors for group: " + myGroupId);
+ }
- int myGroupId = getGroupId(device);
- LeAudioGroupDescriptor descriptor = getGroupDescriptor(myGroupId);
- if (descriptor == null) {
- Log.e(TAG, "no descriptors for group: " + myGroupId);
- return;
+ McpService mcpService = mServiceFactory.getMcpService();
+ if (mcpService != null) {
+ mcpService.setDeviceAuthorized(device, true);
+ }
}
+ // Check if the device is disconnected - if unbond, remove the state machine
+ if (toState == BluetoothProfile.STATE_DISCONNECTED) {
+ int bondState = mAdapterService.getBondState(device);
+ if (bondState == BluetoothDevice.BOND_NONE) {
+ if (DBG) {
+ Log.d(TAG, device + " is unbond. Remove state machine");
+ }
+ removeStateMachine(device);
+ }
- List<BluetoothDevice> connectedDevices = getConnectedPeerDevices(myGroupId);
- /* Let's check if the last connected device is really connected */
- if (connectedDevices.size() == 1 && Objects.equals(
- connectedDevices.get(0), descriptor.mLostLeadDeviceWhileStreaming)) {
- clearLostDevicesWhileStreaming(descriptor);
- return;
- }
+ McpService mcpService = mServiceFactory.getMcpService();
+ if (mcpService != null) {
+ mcpService.setDeviceAuthorized(device, false);
+ }
- if (getConnectedPeerDevices(myGroupId).isEmpty()) {
- descriptor.mIsConnected = false;
+ int myGroupId = getGroupId(device);
+ LeAudioGroupDescriptor descriptor = getGroupDescriptor(myGroupId);
+ if (descriptor == null) {
+ Log.e(TAG, "no descriptors for group: " + myGroupId);
+ return;
+ }
+
+ List<BluetoothDevice> connectedDevices = getConnectedPeerDevices(myGroupId);
+ /* Let's check if the last connected device is really connected */
+ if (connectedDevices.size() == 1 && Objects.equals(
+ connectedDevices.get(0), descriptor.mLostLeadDeviceWhileStreaming)) {
+ clearLostDevicesWhileStreaming(descriptor);
+ return;
+ }
+
+ if (getConnectedPeerDevices(myGroupId).isEmpty()) {
+ descriptor.mIsConnected = false;
+ if (descriptor.mIsActive) {
+ /* Notify Native layer */
+ setActiveDevice(null);
+ descriptor.mIsActive = false;
+ /* Update audio framework */
+ updateActiveDevices(myGroupId,
+ descriptor.mDirection,
+ descriptor.mDirection,
+ descriptor.mIsActive);
+ return;
+ }
+ }
+
if (descriptor.mIsActive) {
- /* Notify Native layer */
- setActiveDevice(null);
- descriptor.mIsActive = false;
- /* Update audio framework */
updateActiveDevices(myGroupId,
descriptor.mDirection,
descriptor.mDirection,
- descriptor.mIsActive,
- hasFallbackDevice);
- return;
+ descriptor.mIsActive);
}
}
+ }
- if (descriptor.mIsActive) {
- updateActiveDevices(myGroupId,
- descriptor.mDirection,
- descriptor.mDirection,
- descriptor.mIsActive);
+ private class ConnectionStateChangedReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED
+ .equals(intent.getAction())) {
+ return;
+ }
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+ int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
+ connectionStateChanged(device, fromState, toState);
}
}
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index 31ba0b1..a7931c0 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -1420,6 +1420,7 @@
int type = c.getInt(c.getColumnIndex(Sms.TYPE));
int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
int read = c.getInt(c.getColumnIndex(Sms.READ));
+ long timestamp = c.getLong(c.getColumnIndex(Sms.DATE));
Msg msg = getMsgListSms().remove(id);
@@ -1427,6 +1428,10 @@
* a message deleted and/or MessageShift for messages deleted by the MCE. */
if (msg == null) {
+ if (BluetoothMapUtils.isDateTimeOlderThanOneYear(timestamp)) {
+ // Skip sending new message events older than one year
+ continue;
+ }
/* New message */
msg = new Msg(id, type, threadId, read);
msgListSms.put(id, msg);
@@ -1435,8 +1440,7 @@
if (mTransmitEvents && // extract contact details only if needed
mMapEventReportVersion
> BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
- String date = BluetoothMapUtils.getDateTimeString(
- c.getLong(c.getColumnIndex(Sms.DATE)));
+ String date = BluetoothMapUtils.getDateTimeString(timestamp);
String subject = c.getString(c.getColumnIndex(Sms.BODY));
if (subject == null) {
subject = "";
@@ -1583,6 +1587,7 @@
// TODO: Go through code to see if we have an issue with mismatch in types
// for threadId. Seems to be a long in DB??
int read = c.getInt(c.getColumnIndex(Mms.READ));
+ long timestamp = c.getLong(c.getColumnIndex(Mms.DATE));
Msg msg = getMsgListMms().remove(id);
@@ -1591,6 +1596,10 @@
* MCE.*/
if (msg == null) {
+ if (BluetoothMapUtils.isDateTimeOlderThanOneYear(timestamp)) {
+ // Skip sending new message events older than one year
+ continue;
+ }
/* New message - only notify on retrieve conf */
listChanged = true;
if (getMmsFolderName(type).equalsIgnoreCase(
@@ -1604,8 +1613,7 @@
if (mTransmitEvents && // extract contact details only if needed
mMapEventReportVersion
!= BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
- String date = BluetoothMapUtils.getDateTimeString(
- c.getLong(c.getColumnIndex(Mms.DATE)));
+ String date = BluetoothMapUtils.getDateTimeString(timestamp);
String subject = c.getString(c.getColumnIndex(Mms.SUBJECT));
if (subject == null || subject.length() == 0) {
/* Get subject from mms text body parts - if any exists */
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java b/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java
index a3710a3..7d7047f 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java
@@ -677,6 +677,21 @@
return format.format(cal.getTime());
}
+ static boolean isDateTimeOlderThanOneYear(long timestamp) {
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(timestamp);
+ Calendar oneYearAgo = Calendar.getInstance();
+ oneYearAgo.add(Calendar.YEAR, -1);
+ if (cal.compareTo(oneYearAgo) > 0) {
+ if (V) {
+ Log.v(TAG, "isDateTimeOlderThanOneYear timestamp : " + timestamp
+ + " oneYearAgo: " + oneYearAgo);
+ }
+ return true;
+ }
+ return false;
+ }
+
static void savePeerSupportUtcTimeStamp(int remoteFeatureMask) {
if ((remoteFeatureMask & MAP_FEATURE_DEFINED_TIMESTAMP_FORMAT_BIT)
== MAP_FEATURE_DEFINED_TIMESTAMP_FORMAT_BIT) {
diff --git a/android/app/src/com/android/bluetooth/mcp/MediaControlGattService.java b/android/app/src/com/android/bluetooth/mcp/MediaControlGattService.java
index 7b44d0c..0f1fef7 100644
--- a/android/app/src/com/android/bluetooth/mcp/MediaControlGattService.java
+++ b/android/app/src/com/android/bluetooth/mcp/MediaControlGattService.java
@@ -24,6 +24,8 @@
import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_WRITE;
import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE;
+import static java.util.Map.entry;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.bluetooth.BluetoothDevice;
@@ -41,7 +43,9 @@
import android.util.Log;
import android.util.Pair;
+import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.hearingaid.HearingAidService;
import com.android.bluetooth.le_audio.LeAudioService;
import com.android.internal.annotations.VisibleForTesting;
@@ -889,6 +893,12 @@
}
if (req.getOpcode() == Request.Opcodes.PLAY) {
+ if (mAdapterService.getActiveDevices(BluetoothProfile.A2DP).size() > 0) {
+ A2dpService.getA2dpService().setActiveDevice(null);
+ }
+ if (mAdapterService.getActiveDevices(BluetoothProfile.HEARING_AID).size() > 0) {
+ HearingAidService.getHearingAidService().setActiveDevice(null);
+ }
if (mLeAudioService == null) {
mLeAudioService = LeAudioService.getLeAudioService();
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
index abe94fb..0854962 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
@@ -17,11 +17,10 @@
package com.android.bluetooth.btservice;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyBoolean;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -60,6 +59,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
@MediumTest
@RunWith(AndroidJUnit4.class)
@@ -86,7 +86,6 @@
@Mock private LeAudioService mLeAudioService;
@Mock private AudioManager mAudioManager;
@Mock private DatabaseManager mDatabaseManager;
- private AudioManager.OnModeChangedListener mAudioModeChangedListener;
@Before
public void setUp() throws Exception {
@@ -99,10 +98,6 @@
MockitoAnnotations.initMocks(this);
TestUtils.setAdapterService(mAdapterService);
- doAnswer(invocation -> {
- mAudioModeChangedListener = invocation.getArgument(1);
- return null;
- }).when(mAudioManager).addOnModeChangedListener(any(), any());
when(mAdapterService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
when(mAdapterService.getSystemServiceName(AudioManager.class))
.thenReturn(Context.AUDIO_SERVICE);
@@ -127,20 +122,24 @@
mDeviceConnectionStack = new ArrayList<>();
mMostRecentDevice = null;
- when(mA2dpService.setActiveDevice(any(), anyBoolean())).thenReturn(true);
+ when(mA2dpService.setActiveDevice(any())).thenReturn(true);
when(mHeadsetService.setActiveDevice(any())).thenReturn(true);
- when(mHearingAidService.setActiveDevice(any(), anyBoolean())).thenReturn(true);
- when(mLeAudioService.setActiveDevice(any(), anyBoolean())).thenReturn(true);
+ when(mHearingAidService.setActiveDevice(any())).thenReturn(true);
+ when(mLeAudioService.setActiveDevice(any())).thenReturn(true);
when(mA2dpService.getFallbackDevice()).thenAnswer(invocation -> {
- int index = Math.max(mDeviceConnectionStack.indexOf(mA2dpDevice),
- mDeviceConnectionStack.indexOf(mA2dpHeadsetDevice));
- return (index == -1) ? null : mDeviceConnectionStack.get(index);
+ if (!mDeviceConnectionStack.isEmpty() && Objects.equals(mA2dpDevice,
+ mDeviceConnectionStack.get(mDeviceConnectionStack.size() - 1))) {
+ return mA2dpDevice;
+ }
+ return null;
});
when(mHeadsetService.getFallbackDevice()).thenAnswer(invocation -> {
- int index = Math.max(mDeviceConnectionStack.indexOf(mHeadsetDevice),
- mDeviceConnectionStack.indexOf(mA2dpHeadsetDevice));
- return (index == -1) ? null : mDeviceConnectionStack.get(index);
+ if (!mDeviceConnectionStack.isEmpty() && Objects.equals(mHeadsetDevice,
+ mDeviceConnectionStack.get(mDeviceConnectionStack.size() - 1))) {
+ return mHeadsetDevice;
+ }
+ return null;
});
when(mDatabaseManager.getMostRecentlyConnectedDevicesInList(any())).thenAnswer(
invocation -> {
@@ -178,7 +177,7 @@
@Test
public void onlyA2dpConnected_setA2dpActive() {
a2dpConnected(mA2dpDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice, false);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
}
/**
@@ -187,10 +186,10 @@
@Test
public void secondA2dpConnected_setSecondA2dpActive() {
a2dpConnected(mA2dpDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice, false);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
a2dpConnected(mA2dpHeadsetDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice, false);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
}
/**
@@ -199,10 +198,10 @@
@Test
public void lastA2dpDisconnected_clearA2dpActive() {
a2dpConnected(mA2dpDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice, false);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
a2dpDisconnected(mA2dpDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(null, false);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
}
/**
@@ -211,15 +210,15 @@
@Test
public void a2dpActiveDeviceSelected_setActive() {
a2dpConnected(mA2dpDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice, false);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
a2dpConnected(mA2dpHeadsetDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice, false);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
a2dpActiveDeviceChanged(mA2dpDevice);
// Don't call mA2dpService.setActiveDevice()
TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
- verify(mA2dpService).setActiveDevice(mA2dpDevice, false);
+ verify(mA2dpService, times(1)).setActiveDevice(mA2dpDevice);
Assert.assertEquals(mA2dpDevice, mActiveDeviceManager.getA2dpActiveDevice());
}
@@ -230,14 +229,14 @@
@Test
public void a2dpSecondDeviceDisconnected_fallbackDeviceActive() {
a2dpConnected(mA2dpDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice, false);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
a2dpConnected(mSecondaryAudioDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice, false);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice);
Mockito.clearInvocations(mA2dpService);
a2dpDisconnected(mSecondaryAudioDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice, false);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
}
/**
@@ -245,8 +244,6 @@
*/
@Test
public void onlyHeadsetConnected_setHeadsetActive() {
- when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);
-
headsetConnected(mHeadsetDevice);
verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
}
@@ -256,8 +253,6 @@
*/
@Test
public void secondHeadsetConnected_setSecondHeadsetActive() {
- when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);
-
headsetConnected(mHeadsetDevice);
verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
@@ -270,8 +265,6 @@
*/
@Test
public void lastHeadsetDisconnected_clearHeadsetActive() {
- when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);
-
headsetConnected(mHeadsetDevice);
verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
@@ -284,8 +277,6 @@
*/
@Test
public void headsetActiveDeviceSelected_setActive() {
- when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);
-
headsetConnected(mHeadsetDevice);
verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
@@ -295,7 +286,7 @@
headsetActiveDeviceChanged(mHeadsetDevice);
// Don't call mHeadsetService.setActiveDevice()
TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
- verify(mHeadsetService).setActiveDevice(mHeadsetDevice);
+ verify(mHeadsetService, times(1)).setActiveDevice(mHeadsetDevice);
Assert.assertEquals(mHeadsetDevice, mActiveDeviceManager.getHfpActiveDevice());
}
@@ -319,33 +310,6 @@
}
/**
- * Test setActiveDevice(null) for both A2dpService and HeadsetService are called when an
- * activated combo (A2DP + Headset) device is disconnected while in call.
- */
- @Test
- public void a2dpHeadsetDisconnected_callsSetActiveDeviceNull() {
- when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
- // A2dpHeadset connected
- headsetConnected(mA2dpHeadsetDevice);
- a2dpConnected(mA2dpHeadsetDevice);
- // Verify activation of A2DP in media mode
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice, false);
-
- // Mode changed to call mode
- when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);
- mAudioModeChangedListener.onModeChanged(AudioManager.MODE_IN_CALL);
- // Verify activation of HFP in call mode
- verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
-
- // A2dpHeadset disconnected
- headsetDisconnected(mA2dpHeadsetDevice);
- a2dpDisconnected(mA2dpHeadsetDevice);
- // Verify setActiveDevice(null) called
- verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(null);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(null, false);
- }
-
- /**
* A combo (A2DP + Headset) device is connected. Then a Hearing Aid is connected.
*/
@Test
@@ -355,11 +319,11 @@
a2dpConnected(mA2dpHeadsetDevice);
headsetConnected(mA2dpHeadsetDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice, false);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
hearingAidActiveDeviceChanged(mHearingAidDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(null, true);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
}
@@ -376,7 +340,7 @@
headsetConnected(mA2dpHeadsetDevice);
TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
- verify(mA2dpService, never()).setActiveDevice(mA2dpHeadsetDevice, false);
+ verify(mA2dpService, never()).setActiveDevice(mA2dpHeadsetDevice);
verify(mHeadsetService, never()).setActiveDevice(mA2dpHeadsetDevice);
}
@@ -393,9 +357,9 @@
a2dpActiveDeviceChanged(mA2dpHeadsetDevice);
TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
- verify(mHearingAidService).setActiveDevice(null, true);
+ verify(mHearingAidService).setActiveDevice(isNull());
// Don't call mA2dpService.setActiveDevice()
- verify(mA2dpService, never()).setActiveDevice(mA2dpHeadsetDevice, false);
+ verify(mA2dpService, never()).setActiveDevice(mA2dpHeadsetDevice);
Assert.assertEquals(mA2dpHeadsetDevice, mActiveDeviceManager.getA2dpActiveDevice());
Assert.assertEquals(null, mActiveDeviceManager.getHearingAidActiveDevice());
}
@@ -413,7 +377,7 @@
headsetActiveDeviceChanged(mA2dpHeadsetDevice);
TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
- verify(mHearingAidService).setActiveDevice(null, true);
+ verify(mHearingAidService).setActiveDevice(isNull());
// Don't call mHeadsetService.setActiveDevice()
verify(mHeadsetService, never()).setActiveDevice(mA2dpHeadsetDevice);
Assert.assertEquals(mA2dpHeadsetDevice, mActiveDeviceManager.getHfpActiveDevice());
@@ -426,7 +390,7 @@
@Test
public void onlyLeAudioConnected_setHeadsetActive() {
leAudioConnected(mLeAudioDevice);
- verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice, false);
+ verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
}
/**
@@ -435,10 +399,10 @@
@Test
public void secondLeAudioConnected_setSecondLeAudioActive() {
leAudioConnected(mLeAudioDevice);
- verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice, false);
+ verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
leAudioConnected(mSecondaryAudioDevice);
- verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice, false);
+ verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice);
}
/**
@@ -447,10 +411,10 @@
@Test
public void lastLeAudioDisconnected_clearLeAudioActive() {
leAudioConnected(mLeAudioDevice);
- verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice, false);
+ verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
leAudioDisconnected(mLeAudioDevice);
- verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(null, false);
+ verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
}
/**
@@ -459,15 +423,15 @@
@Test
public void leAudioActiveDeviceSelected_setActive() {
leAudioConnected(mLeAudioDevice);
- verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice, false);
+ verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
leAudioConnected(mSecondaryAudioDevice);
- verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice, false);
+ verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice);
leAudioActiveDeviceChanged(mLeAudioDevice);
// Don't call mLeAudioService.setActiveDevice()
TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
- verify(mLeAudioService).setActiveDevice(mLeAudioDevice, false);
+ verify(mLeAudioService, times(1)).setActiveDevice(mLeAudioDevice);
Assert.assertEquals(mLeAudioDevice, mActiveDeviceManager.getLeAudioActiveDevice());
}
@@ -478,14 +442,14 @@
@Test
public void leAudioSecondDeviceDisconnected_fallbackDeviceActive() {
leAudioConnected(mLeAudioDevice);
- verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice, false);
+ verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
leAudioConnected(mSecondaryAudioDevice);
- verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice, false);
+ verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice);
Mockito.clearInvocations(mLeAudioService);
leAudioDisconnected(mSecondaryAudioDevice);
- verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice, false);
+ verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
}
/**
@@ -498,11 +462,11 @@
a2dpConnected(mA2dpHeadsetDevice);
headsetConnected(mA2dpHeadsetDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice, false);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
leAudioActiveDeviceChanged(mLeAudioDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(null, true);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
}
@@ -519,7 +483,7 @@
headsetConnected(mA2dpHeadsetDevice);
TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
- verify(mA2dpService).setActiveDevice(mA2dpHeadsetDevice, false);
+ verify(mA2dpService).setActiveDevice(mA2dpHeadsetDevice);
verify(mHeadsetService).setActiveDevice(mA2dpHeadsetDevice);
}
@@ -536,8 +500,8 @@
a2dpActiveDeviceChanged(mA2dpHeadsetDevice);
TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
- verify(mLeAudioService).setActiveDevice(null, true);
- verify(mA2dpService).setActiveDevice(mA2dpHeadsetDevice, false);
+ verify(mLeAudioService).setActiveDevice(isNull());
+ verify(mA2dpService).setActiveDevice(mA2dpHeadsetDevice);
Assert.assertEquals(mA2dpHeadsetDevice, mActiveDeviceManager.getA2dpActiveDevice());
Assert.assertEquals(null, mActiveDeviceManager.getLeAudioActiveDevice());
}
@@ -549,13 +513,13 @@
public void leAudioActive_setHeadsetActiveExplicitly() {
Assume.assumeTrue("Ignore test when LeAudioService is not enabled",
LeAudioService.isEnabled());
- when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);
+
leAudioActiveDeviceChanged(mLeAudioDevice);
headsetConnected(mA2dpHeadsetDevice);
headsetActiveDeviceChanged(mA2dpHeadsetDevice);
TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
- verify(mLeAudioService).setActiveDevice(null, false);
+ verify(mLeAudioService).setActiveDevice(isNull());
verify(mHeadsetService).setActiveDevice(mA2dpHeadsetDevice);
Assert.assertEquals(mA2dpHeadsetDevice, mActiveDeviceManager.getHfpActiveDevice());
Assert.assertEquals(null, mActiveDeviceManager.getLeAudioActiveDevice());
@@ -570,15 +534,15 @@
when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
leAudioConnected(mLeAudioDevice);
- verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice, false);
+ verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
a2dpConnected(mA2dpDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice, false);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
Mockito.clearInvocations(mLeAudioService);
a2dpDisconnected(mA2dpDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS).atLeast(1)).setActiveDevice(null, true);
- verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice, false);
+ verify(mA2dpService, timeout(TIMEOUT_MS).atLeast(1)).setActiveDevice(isNull());
+ verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
}
/**
@@ -590,15 +554,15 @@
when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
a2dpConnected(mA2dpDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice, false);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
leAudioConnected(mLeAudioDevice);
- verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice, false);
+ verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
Mockito.clearInvocations(mA2dpService);
leAudioDisconnected(mLeAudioDevice);
- verify(mLeAudioService, timeout(TIMEOUT_MS).atLeast(1)).setActiveDevice(null, true);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice, false);
+ verify(mLeAudioService, timeout(TIMEOUT_MS).atLeast(1)).setActiveDevice(isNull());
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
}
/**
@@ -608,15 +572,14 @@
@Test
public void hearingAidSecondDeviceDisconnected_fallbackDeviceActive() {
hearingAidConnected(mHearingAidDevice);
- verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(mHearingAidDevice, false);
+ verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(mHearingAidDevice);
hearingAidConnected(mSecondaryAudioDevice);
- verify(mHearingAidService, timeout(TIMEOUT_MS))
- .setActiveDevice(mSecondaryAudioDevice, false);
+ verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice);
Mockito.clearInvocations(mHearingAidService);
hearingAidDisconnected(mSecondaryAudioDevice);
- verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(mHearingAidDevice, false);
+ verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(mHearingAidDevice);
}
/**
@@ -628,7 +591,7 @@
when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
hearingAidConnected(mHearingAidDevice);
- verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(mHearingAidDevice, false);
+ verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(mHearingAidDevice);
leAudioConnected(mLeAudioDevice);
a2dpConnected(mA2dpDevice);
@@ -636,14 +599,14 @@
a2dpActiveDeviceChanged(mA2dpDevice);
TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
- verify(mHearingAidService).setActiveDevice(null, true);
+ verify(mHearingAidService).setActiveDevice(isNull());
verify(mLeAudioService, never()).setActiveDevice(mLeAudioDevice);
- verify(mA2dpService, never()).setActiveDevice(mA2dpDevice, false);
+ verify(mA2dpService, never()).setActiveDevice(mA2dpDevice);
a2dpDisconnected(mA2dpDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS).atLeast(1)).setActiveDevice(null, true);
+ verify(mA2dpService, timeout(TIMEOUT_MS).atLeast(1)).setActiveDevice(isNull());
verify(mHearingAidService, timeout(TIMEOUT_MS).times(2))
- .setActiveDevice(mHearingAidDevice, false);
+ .setActiveDevice(mHearingAidDevice);
}
/**
@@ -656,7 +619,7 @@
verify(mLeAudioService, never()).setActiveDevice(mLeHearingAidDevice);
leAudioConnected(mLeHearingAidDevice);
- verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeHearingAidDevice, false);
+ verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeHearingAidDevice);
}
/**
@@ -667,7 +630,7 @@
public void leAudioConnectedAfterLeHearingAid_setLeAudioActiveShouldNotBeCalled() {
leHearingAidConnected(mLeHearingAidDevice);
leAudioConnected(mLeHearingAidDevice);
- verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeHearingAidDevice, false);
+ verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeHearingAidDevice);
leAudioConnected(mLeAudioDevice);
TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
@@ -684,24 +647,24 @@
when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
hearingAidConnected(mHearingAidDevice);
- verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(mHearingAidDevice, false);
+ verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(mHearingAidDevice);
leHearingAidConnected(mLeHearingAidDevice);
leAudioConnected(mLeHearingAidDevice);
- verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeHearingAidDevice, false);
+ verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeHearingAidDevice);
a2dpConnected(mA2dpDevice);
TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
- verify(mA2dpService, never()).setActiveDevice(mA2dpDevice, false);
+ verify(mA2dpService, never()).setActiveDevice(mA2dpDevice);
Mockito.clearInvocations(mHearingAidService, mA2dpService);
leHearingAidDisconnected(mLeHearingAidDevice);
leAudioDisconnected(mLeHearingAidDevice);
- verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(mHearingAidDevice, false);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(null, true);
+ verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(mHearingAidDevice);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
hearingAidDisconnected(mHearingAidDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice, false);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
}
/**
@@ -711,57 +674,13 @@
public void wiredAudioDeviceConnected_setAllActiveDevicesNull() {
a2dpConnected(mA2dpDevice);
headsetConnected(mHeadsetDevice);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice, false);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
mActiveDeviceManager.wiredAudioDeviceConnected();
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(null, false);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
- verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(null, false);
- }
-
- @Test
- public void leAudioActive_whileMediaPlayback_useLeAudioForMediaAndHeadsetForCall() {
- leAudioConnected(mLeAudioDevice);
- a2dpConnected(mA2dpDevice);
- headsetConnected(mHeadsetDevice);
- verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice, false);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice, false);
- verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
-
- Mockito.clearInvocations(mLeAudioService, mA2dpService, mHeadsetService);
- leAudioActiveDeviceChanged(mLeAudioDevice);
- TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
- verify(mA2dpService).setActiveDevice(null, true);
- verify(mHeadsetService).setActiveDevice(null);
-
- Mockito.clearInvocations(mLeAudioService, mA2dpService, mHeadsetService);
- mAudioModeChangedListener.onModeChanged(AudioManager.MODE_IN_CALL);
- verify(mHeadsetService).setActiveDevice(mHeadsetDevice);
- verify(mLeAudioService).setActiveDevice(null, true);
- }
-
- @Test
- public void leAudioActive_whileInCall_useLeAudioForCallAndHeadsetForMedia() {
- when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);
-
- leAudioConnected(mLeAudioDevice);
- headsetConnected(mHeadsetDevice);
- a2dpConnected(mA2dpDevice);
- verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice, false);
- verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice, false);
- verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
-
- Mockito.clearInvocations(mLeAudioService, mA2dpService, mHeadsetService);
- leAudioActiveDeviceChanged(mLeAudioDevice);
- TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
- verify(mA2dpService).setActiveDevice(null, true);
- verify(mHeadsetService).setActiveDevice(null);
-
- Mockito.clearInvocations(mLeAudioService, mA2dpService, mHeadsetService);
- mAudioModeChangedListener.onModeChanged(AudioManager.MODE_NORMAL);
- verify(mA2dpService).setActiveDevice(mA2dpDevice, false);
- verify(mLeAudioService).setActiveDevice(null, true);
+ verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
}
/**
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
index 44d0c8d..cf74dfd 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
@@ -183,10 +183,6 @@
if (newState == BluetoothProfile.STATE_CONNECTED) {
// ActiveDeviceManager calls setActiveDevice when connected.
mService.setActiveDevice(device);
- } else if (newState == BluetoothProfile.STATE_DISCONNECTED
- && mService.getConnectedDevices().isEmpty()) {
- // ActiveDeviceManager calls setActiveDevice(null) when all devices are disconnected.
- mService.setActiveDevice(null);
}
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
index 8f14e37..fcb6779 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
@@ -32,7 +32,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.NonNull;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeAudio;
@@ -48,6 +47,7 @@
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.BluetoothProfileConnectionInfo;
+import android.os.Parcel;
import android.os.ParcelUuid;
import androidx.test.InstrumentationRegistry;
@@ -55,12 +55,14 @@
import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;
+import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.vc.VolumeControlService;
import org.junit.After;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -281,24 +283,15 @@
}
}
- private void verifyConnectionStateIntent(int timeoutMs, @NonNull BluetoothDevice device,
+ private void verifyConnectionStateIntent(int timeoutMs, BluetoothDevice device,
int newState, int prevState) {
Intent intent = TestUtils.waitForIntent(timeoutMs, mDeviceQueueMap.get(device));
assertThat(intent).isNotNull();
assertThat(intent.getAction())
.isEqualTo(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
assertThat((BluetoothDevice)intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)).isEqualTo(device);
- int newConnectionState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
- assertThat(newConnectionState).isEqualTo(newState);
+ assertThat(intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1)).isEqualTo(newState);
assertThat(intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1)).isEqualTo(prevState);
-
- if (newConnectionState == BluetoothProfile.STATE_CONNECTED) {
- // ActiveDeviceManager calls deviceConnected when connected.
- mService.deviceConnected(device);
- } else if (newConnectionState == BluetoothProfile.STATE_DISCONNECTED) {
- // ActiveDeviceManager calls deviceDisconnected when connected.
- mService.deviceDisconnected(device, false);
- }
}
/**
@@ -1353,6 +1346,7 @@
verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(any(), eq(leadDevice),
any(BluetoothProfileConnectionInfo.class));
+
}
/**
diff --git a/system/bta/Android.bp b/system/bta/Android.bp
index 62620a0..c33a732 100644
--- a/system/bta/Android.bp
+++ b/system/bta/Android.bp
@@ -606,6 +606,7 @@
"le_audio/metrics_collector_linux.cc",
"le_audio/mock_iso_manager.cc",
"test/common/mock_controller.cc",
+ "test/common/mock_csis_client.cc",
"le_audio/state_machine.cc",
"le_audio/state_machine_test.cc",
"le_audio/storage_helper.cc",
diff --git a/system/bta/csis/csis_client.cc b/system/bta/csis/csis_client.cc
index a210dc4..4ef353f 100644
--- a/system/bta/csis/csis_client.cc
+++ b/system/bta/csis/csis_client.cc
@@ -525,6 +525,16 @@
}
}
+ int GetDesiredSize(int group_id) override {
+ auto csis_group = FindCsisGroup(group_id);
+ if (!csis_group) {
+ LOG_INFO("Unknown group %d", group_id);
+ return -1;
+ }
+
+ return csis_group->GetDesiredSize();
+ }
+
bool SerializeSets(const RawAddress& addr, std::vector<uint8_t>& out) const {
auto device = FindDeviceByAddress(addr);
if (device == nullptr) {
diff --git a/system/bta/csis/csis_types.h b/system/bta/csis/csis_types.h
index 466a1d4..75783a4 100644
--- a/system/bta/csis/csis_types.h
+++ b/system/bta/csis/csis_types.h
@@ -64,7 +64,7 @@
/* CSIS Types */
static constexpr uint8_t kDefaultScanDurationS = 5;
-static constexpr uint8_t kDefaultCsisSetSize = 2;
+static constexpr uint8_t kDefaultCsisSetSize = 1;
static constexpr uint8_t kUnknownRank = 0xff;
/* Enums */
diff --git a/system/bta/include/bta_csis_api.h b/system/bta/include/bta_csis_api.h
index fe864f5..44dca04 100644
--- a/system/bta/include/bta_csis_api.h
+++ b/system/bta/include/bta_csis_api.h
@@ -47,6 +47,7 @@
bluetooth::Uuid uuid = bluetooth::groups::kGenericContextUuid) = 0;
virtual void LockGroup(int group_id, bool lock, CsisLockCb cb) = 0;
virtual std::vector<RawAddress> GetDeviceList(int group_id) = 0;
+ virtual int GetDesiredSize(int group_id) = 0;
};
} // namespace csis
} // namespace bluetooth
diff --git a/system/bta/le_audio/devices.cc b/system/bta/le_audio/devices.cc
index debd733..50944c2 100644
--- a/system/bta/le_audio/devices.cc
+++ b/system/bta/le_audio/devices.cc
@@ -22,6 +22,7 @@
#include <map>
#include "audio_hal_client/audio_hal_client.h"
+#include "bta_csis_api.h"
#include "bta_gatt_queue.h"
#include "bta_groups.h"
#include "bta_le_audio_api.h"
@@ -967,10 +968,16 @@
uint8_t cis_count_bidir = 0;
uint8_t cis_count_unidir_sink = 0;
uint8_t cis_count_unidir_source = 0;
- get_cis_count(confs, GetGroupStrategy(),
+ int csis_group_size =
+ bluetooth::csis::CsisClient::Get()->GetDesiredSize(group_id_);
+ /* If this is CSIS group, the csis_group_size will be > 0, otherwise -1.
+ * If the last happen it means, group size is 1 */
+ int group_size = csis_group_size > 0 ? csis_group_size : 1;
+
+ get_cis_count(*confs, group_size, GetGroupStrategy(),
GetAseCount(types::kLeAudioDirectionSink),
- GetAseCount(types::kLeAudioDirectionSource), &cis_count_bidir,
- &cis_count_unidir_sink, &cis_count_unidir_source);
+ GetAseCount(types::kLeAudioDirectionSource), cis_count_bidir,
+ cis_count_unidir_sink, cis_count_unidir_source);
uint8_t idx = 0;
while (cis_count_bidir > 0) {
@@ -980,7 +987,6 @@
.type = CisType::CIS_TYPE_BIDIRECTIONAL,
.conn_handle = 0,
};
-
cises_.push_back(cis_entry);
cis_count_bidir--;
idx++;
@@ -1045,18 +1051,21 @@
cis_id = GetFirstFreeCisId(CisType::CIS_TYPE_BIDIRECTIONAL);
}
- if (cis_id == kInvalidCisId) {
- LOG_ERROR(" Unable to get free Bi-Directional CIS ID");
- return false;
+ if (cis_id != kInvalidCisId) {
+ ase->cis_id = cis_id;
+ matching_bidir_ase->cis_id = cis_id;
+ cises_[cis_id].addr = leAudioDevice->address_;
+
+ LOG_INFO(
+ " ASE ID: %d and ASE ID: %d, assigned Bi-Directional CIS ID: %d",
+ +ase->id, +matching_bidir_ase->id, +ase->cis_id);
+ continue;
}
- ase->cis_id = cis_id;
- matching_bidir_ase->cis_id = cis_id;
- cises_[cis_id].addr = leAudioDevice->address_;
-
- LOG_INFO(" ASE ID: %d and ASE ID: %d, assigned Bi-Directional CIS ID: %d",
- +ase->id, +matching_bidir_ase->id, +ase->cis_id);
- continue;
+ LOG_WARN(
+ " ASE ID: %d, unable to get free Bi-Directional CIS ID but maybe "
+ "thats fine. Try using unidirectional.",
+ ase->id);
}
if (ase->direction == types::kLeAudioDirectionSink) {
@@ -1267,7 +1276,7 @@
if (ent.direction == types::kLeAudioDirectionSink &&
strategy != required_snk_strategy) {
- LOG_INFO(" Sink strategy mismatch (%d!=%d)",
+ LOG_INFO(" Sink strategy mismatch group!=cfg.entry (%d!=%d)",
static_cast<int>(required_snk_strategy),
static_cast<int>(strategy));
return false;
@@ -1545,12 +1554,12 @@
std::vector<uint8_t>());
}
- DLOG(INFO) << __func__ << " device=" << address_
- << ", activated ASE id=" << +ase->id
- << ", direction=" << +ase->direction
- << ", max_sdu_size=" << +ase->max_sdu_size
- << ", cis_id=" << +ase->cis_id
- << ", target_latency=" << +ent.target_latency;
+ LOG_DEBUG(
+ "device=%s, activated ASE id=%d, direction=%s, max_sdu_size=%d, "
+ "cis_id=%d, target_latency=%d",
+ address_.ToString().c_str(), ase->id,
+ (ent.direction == 1 ? "snk" : "src"), ase->max_sdu_size, ase->cis_id,
+ ent.target_latency);
/* Try to use the already active ASE */
ase = GetNextActiveAseWithSameDirection(ase);
@@ -1589,9 +1598,9 @@
types::AudioLocations group_src_audio_locations = 0;
for (const auto& ent : (*audio_set_conf).confs) {
- DLOG(INFO) << __func__
- << " Looking for requirements: " << audio_set_conf->name << " - "
- << (ent.direction == 1 ? "snk" : "src");
+ LOG_DEBUG(" Looking for requirements: %s, - %s",
+ audio_set_conf->name.c_str(),
+ (ent.direction == 1 ? "snk" : "src"));
uint8_t required_device_cnt = ent.device_cnt;
uint8_t max_required_ase_per_dev =
@@ -1599,10 +1608,11 @@
uint8_t active_ase_num = 0;
le_audio::types::LeAudioConfigurationStrategy strategy = ent.strategy;
- DLOG(INFO) << __func__ << " Number of devices: " << +required_device_cnt
- << " number of ASEs: " << +ent.ase_cnt
- << " Max ASE per device: " << +max_required_ase_per_dev
- << " strategy: " << (int)strategy;
+ LOG_DEBUG(
+ "Number of devices: %d number of ASEs: %d, Max ASE per device: %d "
+ "strategy: %d",
+ required_device_cnt, ent.ase_cnt, max_required_ase_per_dev,
+ (int)strategy);
for (auto* device = GetFirstDeviceWithActiveContext(context_type);
device != nullptr && required_device_cnt > 0;
@@ -1629,14 +1639,14 @@
if (required_device_cnt > 0) {
/* Don't left any active devices if requirements are not met */
- LOG(ERROR) << __func__ << " could not configure all the devices";
+ LOG_ERROR(" could not configure all the devices");
Deactivate();
return false;
}
}
- LOG(INFO) << "Choosed ASE Configuration for group: " << this->group_id_
- << " configuration: " << audio_set_conf->name;
+ LOG_INFO("Choosed ASE Configuration for group: %d, configuration: %s",
+ group_id_, audio_set_conf->name.c_str());
configuration_context_type_ = context_type;
metadata_context_type_ = metadata_context_type;
@@ -1912,21 +1922,22 @@
const set_configurations::AudioSetConfiguration* conf =
available_context_to_configuration_map[context_type];
- DLOG(INFO) << __func__;
-
if (!conf) {
- LOG(ERROR) << __func__ << ", requested context type: "
- << loghex(static_cast<uint16_t>(context_type))
- << ", is in mismatch with cached available contexts";
+ LOG_ERROR(
+ ", requested context type: %s , is in mismatch with cached available "
+ "contexts ",
+ bluetooth::common::ToString(context_type).c_str());
return false;
}
- DLOG(INFO) << __func__ << " setting context type: " << int(context_type);
+ LOG_DEBUG(" setting context type: %s",
+ bluetooth::common::ToString(context_type).c_str());
if (!ConfigureAses(conf, context_type, metadata_context_type, ccid_list)) {
- LOG(ERROR) << __func__ << ", requested pick ASE config context type: "
- << loghex(static_cast<uint16_t>(context_type))
- << ", is in mismatch with cached available contexts";
+ LOG_ERROR(
+ ", requested context type: %s , is in mismatch with cached available "
+ "contexts",
+ bluetooth::common::ToString(context_type).c_str());
return false;
}
diff --git a/system/bta/le_audio/devices_test.cc b/system/bta/le_audio/devices_test.cc
index 8dbedf6..a7a3cda 100644
--- a/system/bta/le_audio/devices_test.cc
+++ b/system/bta/le_audio/devices_test.cc
@@ -24,6 +24,8 @@
#include "le_audio_set_configuration_provider.h"
#include "le_audio_types.h"
#include "mock_controller.h"
+#include "mock_csis_client.h"
+#include "os/log.h"
#include "stack/btm/btm_int_types.h"
tACL_CONN* btm_bda_to_acl(const RawAddress& bda, tBT_TRANSPORT transport) {
@@ -42,6 +44,9 @@
using ::le_audio::types::AseState;
using ::le_audio::types::AudioContexts;
using ::le_audio::types::LeAudioContextType;
+using testing::_;
+using testing::Invoke;
+using testing::Return;
using testing::Test;
RawAddress GetTestAddress(int index) {
@@ -399,12 +404,23 @@
bluetooth::manager::SetMockBtmInterface(&btm_interface_);
controller::SetMockControllerInterface(&controller_interface_);
::le_audio::AudioSetConfigurationProvider::Initialize();
+ MockCsisClient::SetMockInstanceForTesting(&mock_csis_client_module_);
+ ON_CALL(mock_csis_client_module_, Get())
+ .WillByDefault(Return(&mock_csis_client_module_));
+ ON_CALL(mock_csis_client_module_, IsCsisClientRunning())
+ .WillByDefault(Return(true));
+ ON_CALL(mock_csis_client_module_, GetDeviceList(_))
+ .WillByDefault(Invoke([this](int group_id) { return addresses_; }));
+ ON_CALL(mock_csis_client_module_, GetDesiredSize(_))
+ .WillByDefault(
+ Invoke([this](int group_id) { return (int)(addresses_.size()); }));
}
void TearDown() override {
controller::SetMockControllerInterface(nullptr);
bluetooth::manager::SetMockBtmInterface(nullptr);
devices_.clear();
+ addresses_.clear();
delete group_;
::le_audio::AudioSetConfigurationProvider::Cleanup();
}
@@ -416,6 +432,10 @@
auto device = (std::make_shared<LeAudioDevice>(
GetTestAddress(index), DeviceConnectState::DISCONNECTED));
devices_.push_back(device);
+ LOG_INFO(" addresses %d", (int)(addresses_.size()));
+ addresses_.push_back(device->address_);
+ LOG_INFO(" Addresses %d", (int)(addresses_.size()));
+
group_->AddNode(device);
int ase_id = 1;
@@ -760,9 +780,11 @@
const int group_id_ = 6;
std::vector<std::shared_ptr<LeAudioDevice>> devices_;
+ std::vector<RawAddress> addresses_;
LeAudioDeviceGroup* group_ = nullptr;
bluetooth::manager::MockBtmInterface btm_interface_;
controller::MockControllerInterface controller_interface_;
+ MockCsisClient mock_csis_client_module_;
};
TEST_F(LeAudioAseConfigurationTest, test_mono_speaker_ringtone) {
diff --git a/system/bta/le_audio/le_audio_client_test.cc b/system/bta/le_audio/le_audio_client_test.cc
index 8d8d1c4..5acdeed 100644
--- a/system/bta/le_audio/le_audio_client_test.cc
+++ b/system/bta/le_audio/le_audio_client_test.cc
@@ -3358,6 +3358,9 @@
codec_spec_conf::kLeAudioLocationFrontRight, group_size,
group_id, 2 /* rank*/, true /*connect_through_csis*/);
+ ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id))
+ .WillByDefault(Invoke([&](int group_id) { return 2; }));
+
// Start streaming
EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _)).Times(1);
EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _)).Times(1);
@@ -3428,6 +3431,9 @@
codec_spec_conf::kLeAudioLocationFrontRight, group_size,
group_id, 2 /* rank*/, true /*connect_through_csis*/);
+ ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id))
+ .WillByDefault(Invoke([&](int group_id) { return 2; }));
+
// Start streaming
EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _)).Times(1);
EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _)).Times(1);
@@ -3526,6 +3532,9 @@
constexpr int gmcs_ccid = 1;
constexpr int gtbs_ccid = 2;
+ ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id))
+ .WillByDefault(Invoke([&](int group_id) { return 2; }));
+
// Start streaming MEDIA
EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _)).Times(1);
EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _)).Times(1);
@@ -3575,8 +3584,10 @@
ON_CALL(mock_csis_client_module_, IsCsisClientRunning())
.WillByDefault(Return(true));
- // First earbud
const RawAddress test_address0 = GetTestAddress(0);
+ const RawAddress test_address1 = GetTestAddress(1);
+
+ // First earbud
ConnectCsisDevice(test_address0, 1 /*conn_id*/,
codec_spec_conf::kLeAudioLocationFrontLeft,
codec_spec_conf::kLeAudioLocationFrontLeft, group_size,
@@ -3587,6 +3598,9 @@
EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _)).Times(1);
LeAudioClient::Get()->GroupSetActive(group_id);
+ ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id))
+ .WillByDefault(Invoke([&](int group_id) { return 2; }));
+
StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id);
Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_);
@@ -3599,7 +3613,6 @@
TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
// Second earbud connects during stream
- const RawAddress test_address1 = GetTestAddress(1);
ConnectCsisDevice(test_address1, 2 /*conn_id*/,
codec_spec_conf::kLeAudioLocationFrontRight,
codec_spec_conf::kLeAudioLocationFrontRight, group_size,
@@ -3637,6 +3650,9 @@
codec_spec_conf::kLeAudioLocationFrontRight, group_size,
group_id, 2 /* rank*/, true /*connect_through_csis*/);
+ ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id))
+ .WillByDefault(Invoke([&](int group_id) { return 2; }));
+
// Audio sessions are started only when device gets active
EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _)).Times(1);
EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _)).Times(1);
@@ -3705,6 +3721,9 @@
codec_spec_conf::kLeAudioLocationFrontRight, group_size,
group_id, 2 /* rank*/, true /*connect_through_csis*/);
+ ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id))
+ .WillByDefault(Invoke([&](int group_id) { return 2; }));
+
// Audio sessions are started only when device gets active
EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _)).Times(1);
EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _)).Times(1);
diff --git a/system/bta/le_audio/le_audio_types.cc b/system/bta/le_audio/le_audio_types.cc
index c0d6ed5..29d69ec 100644
--- a/system/bta/le_audio/le_audio_types.cc
+++ b/system/bta/le_audio/le_audio_types.cc
@@ -70,124 +70,154 @@
return curr_min_req_devices_cnt;
}
-void get_cis_count(const AudioSetConfigurations* audio_set_confs,
- types::LeAudioConfigurationStrategy strategy,
- int group_ase_snk_cnt, int group_ase_src_count,
- uint8_t* cis_count_bidir, uint8_t* cis_count_unidir_sink,
- uint8_t* cis_count_unidir_source) {
- LOG_INFO(" strategy %d, sink ases: %d, source ases %d",
- static_cast<int>(strategy), group_ase_snk_cnt, group_ase_src_count);
+inline void get_cis_count(const AudioSetConfiguration& audio_set_conf,
+ int expected_device_cnt,
+ types::LeAudioConfigurationStrategy strategy,
+ int avail_group_sink_ase_count,
+ int avail_group_source_ase_count,
+ uint8_t& out_current_cis_count_bidir,
+ uint8_t& out_current_cis_count_unidir_sink,
+ uint8_t& out_current_cis_count_unidir_source) {
+ LOG_INFO("%s", audio_set_conf.name.c_str());
- for (auto audio_set_conf : *audio_set_confs) {
- std::pair<uint8_t /* sink */, uint8_t /* source */> snk_src_pair(0, 0);
- uint8_t bidir_count = 0;
- uint8_t unidir_sink_count = 0;
- uint8_t unidir_source_count = 0;
+ /* Sum up the requirements from all subconfigs. They usually have different
+ * directions.
+ */
+ types::BidirectionalPair<uint8_t> config_ase_count = {0, 0};
+ int config_device_cnt = 0;
- LOG_INFO("%s ", audio_set_conf->name.c_str());
- bool stategy_mismatch = false;
- for (auto ent : (*audio_set_conf).confs) {
- if (ent.strategy != strategy) {
- LOG_DEBUG("Strategy does not match (%d != %d)- skip this configuration",
- static_cast<int>(ent.strategy), static_cast<int>(strategy));
- stategy_mismatch = true;
- break;
- }
- if (ent.direction == kLeAudioDirectionSink) {
- snk_src_pair.first += ent.ase_cnt;
- }
- if (ent.direction == kLeAudioDirectionSource) {
- snk_src_pair.second += ent.ase_cnt;
- }
+ for (auto ent : audio_set_conf.confs) {
+ if ((ent.direction == kLeAudioDirectionSink) &&
+ (ent.strategy != strategy)) {
+ LOG_DEBUG("Strategy does not match (%d != %d)- skip this configuration",
+ static_cast<int>(ent.strategy), static_cast<int>(strategy));
+ return;
}
- if (stategy_mismatch) {
- continue;
+ /* Sum up sink and source ases */
+ if (ent.direction == kLeAudioDirectionSink) {
+ config_ase_count.sink += ent.ase_cnt;
+ }
+ if (ent.direction == kLeAudioDirectionSource) {
+ config_ase_count.source += ent.ase_cnt;
}
- /* Before we start adding another CISes, ignore scenarios which cannot
- * satisfied because of the number of ases
- */
+ /* Calculate the max device count */
+ config_device_cnt =
+ std::max(static_cast<uint8_t>(config_device_cnt), ent.device_cnt);
+ }
- if (group_ase_snk_cnt == 0 && snk_src_pair.first > 0) {
- LOG_DEBUG("Group does not have sink ASEs");
- continue;
+ LOG_DEBUG("Config sink ases: %d, source ases: %d, device count: %d",
+ config_ase_count.sink, config_ase_count.source, config_device_cnt);
+
+ /* Reject configurations not matching our device count */
+ if (expected_device_cnt != config_device_cnt) {
+ LOG_DEBUG(" Device cnt %d != %d", expected_device_cnt, config_device_cnt);
+ return;
+ }
+
+ /* Reject configurations requiring sink ASES if our group has none */
+ if ((avail_group_sink_ase_count == 0) && (config_ase_count.sink > 0)) {
+ LOG_DEBUG("Group does not have sink ASEs");
+ return;
+ }
+
+ /* Reject configurations requiring source ASES if our group has none */
+ if ((avail_group_source_ase_count == 0) && (config_ase_count.source > 0)) {
+ LOG_DEBUG("Group does not have source ASEs");
+ return;
+ }
+
+ /* If expected group size is 1, then make sure device has enough ASEs */
+ if (expected_device_cnt == 1) {
+ if ((config_ase_count.sink > avail_group_sink_ase_count) ||
+ (config_ase_count.source > avail_group_source_ase_count)) {
+ LOG_DEBUG("Single device group with not enought sink/source ASEs");
+ return;
}
+ }
- if (group_ase_src_count == 0 && snk_src_pair.second > 0) {
- LOG_DEBUG("Group does not have source ASEs");
- continue;
- }
-
- /* Configuration list is set in the prioritized order.
- * it might happen that more prio configuration can be supported
- * and is already taken into account.
- * Now let's try to ignore ortogonal configuration which would just
- * increase our demant on number of CISes but will never happen
- */
-
- if (snk_src_pair.first == 0 &&
- (*cis_count_unidir_sink > 0 || *cis_count_bidir > 0)) {
- LOG_DEBUG(
- "More prio configuration using sink ASEs has been taken into "
- "account");
- continue;
- }
- if (snk_src_pair.second == 0 &&
- (*cis_count_unidir_source > 0 || *cis_count_bidir > 0)) {
- LOG_DEBUG(
- "More prio configuration using source ASEs has been taken into "
- "account");
- continue;
- }
-
- bidir_count = std::min(snk_src_pair.first, snk_src_pair.second);
- unidir_sink_count = ((snk_src_pair.first - bidir_count) > 0)
- ? (snk_src_pair.first - bidir_count)
- : 0;
- unidir_source_count = ((snk_src_pair.second - bidir_count) > 0)
- ? (snk_src_pair.second - bidir_count)
- : 0;
-
- *cis_count_bidir = std::max(bidir_count, *cis_count_bidir);
-
- /* Check if we can reduce a number of unicast CISes in case bidirectional
- * are use in other or this scenario */
- if (bidir_count < *cis_count_bidir) {
- /* Since we already have bidirectional cises available from other
- * scenarios, let's decrease number of unicast sinks in this scenario.
- */
- uint8_t available_bidir = *cis_count_bidir - bidir_count;
- unidir_sink_count =
- unidir_sink_count - std::min(unidir_sink_count, available_bidir);
- unidir_source_count =
- unidir_source_count - std::min(unidir_source_count, available_bidir);
- } else if (bidir_count > *cis_count_bidir) {
- /* Lets decrease number of the unicast cises from previouse scenarios */
- uint8_t available_bidir = bidir_count - *cis_count_bidir;
- *cis_count_unidir_sink =
- *cis_count_unidir_sink -
- std::min(*cis_count_unidir_sink, available_bidir);
- *cis_count_unidir_source =
- *cis_count_unidir_source -
- std::min(*cis_count_unidir_source, available_bidir);
- }
-
- *cis_count_unidir_sink =
- std::max(unidir_sink_count, *cis_count_unidir_sink);
- *cis_count_unidir_source =
- std::max(unidir_source_count, *cis_count_unidir_source);
-
+ /* Configuration list is set in the prioritized order.
+ * it might happen that a higher prio configuration can be supported
+ * and is already taken into account (out_current_cis_count_* is non zero).
+ * Now let's try to ignore ortogonal configuration which would just
+ * increase our demant on number of CISes but will never happen
+ */
+ if (config_ase_count.sink == 0 && (out_current_cis_count_unidir_sink > 0 ||
+ out_current_cis_count_bidir > 0)) {
LOG_INFO(
+ "Higher prio configuration using sink ASEs has been taken into "
+ "account");
+ return;
+ }
+
+ if (config_ase_count.source == 0 &&
+ (out_current_cis_count_unidir_source > 0 ||
+ out_current_cis_count_bidir > 0)) {
+ LOG_INFO(
+ "Higher prio configuration using source ASEs has been taken into "
+ "account");
+ return;
+ }
+
+ /* Check how many bidirectional cises we can use */
+ uint8_t config_bidir_cis_count =
+ std::min(config_ase_count.sink, config_ase_count.source);
+ /* Count the remaining unidirectional cises */
+ uint8_t config_unidir_sink_cis_count =
+ config_ase_count.sink - config_bidir_cis_count;
+ uint8_t config_unidir_source_cis_count =
+ config_ase_count.source - config_bidir_cis_count;
+
+ /* WARNING: Minipolicy which prioritizes bidirectional configs */
+ if (config_bidir_cis_count > out_current_cis_count_bidir) {
+ /* Correct all counters to represent this single config */
+ out_current_cis_count_bidir = config_bidir_cis_count;
+ out_current_cis_count_unidir_sink = config_unidir_sink_cis_count;
+ out_current_cis_count_unidir_source = config_unidir_source_cis_count;
+
+ } else if (out_current_cis_count_bidir == 0) {
+ /* No bidirectionals possible yet. Calculate for unidirectional cises. */
+ if ((out_current_cis_count_unidir_sink == 0) &&
+ (out_current_cis_count_unidir_source == 0)) {
+ out_current_cis_count_unidir_sink = config_unidir_sink_cis_count;
+ out_current_cis_count_unidir_source = config_unidir_source_cis_count;
+ }
+ }
+}
+
+void get_cis_count(const AudioSetConfigurations& audio_set_confs,
+ int expected_device_cnt,
+ types::LeAudioConfigurationStrategy strategy,
+ int avail_group_ase_snk_cnt, int avail_group_ase_src_count,
+ uint8_t& out_cis_count_bidir,
+ uint8_t& out_cis_count_unidir_sink,
+ uint8_t& out_cis_count_unidir_source) {
+ LOG_INFO(
+ " strategy %d, group avail sink ases: %d, group avail source ases %d "
+ "expected_device_count %d",
+ static_cast<int>(strategy), avail_group_ase_snk_cnt,
+ avail_group_ase_src_count, expected_device_cnt);
+
+ /* Look for the most optimal configuration and store the needed cis counts */
+ for (auto audio_set_conf : audio_set_confs) {
+ get_cis_count(*audio_set_conf, expected_device_cnt, strategy,
+ avail_group_ase_snk_cnt, avail_group_ase_src_count,
+ out_cis_count_bidir, out_cis_count_unidir_sink,
+ out_cis_count_unidir_source);
+
+ LOG_DEBUG(
"Intermediate step: Bi-Directional: %d,"
" Uni-Directional Sink: %d, Uni-Directional Source: %d ",
- *cis_count_bidir, *cis_count_unidir_sink, *cis_count_unidir_source);
+ out_cis_count_bidir, out_cis_count_unidir_sink,
+ out_cis_count_unidir_source);
}
LOG_INFO(
" Maximum CIS count, Bi-Directional: %d,"
" Uni-Directional Sink: %d, Uni-Directional Source: %d",
- *cis_count_bidir, *cis_count_unidir_sink, *cis_count_unidir_source);
+ out_cis_count_bidir, out_cis_count_unidir_sink,
+ out_cis_count_unidir_source);
}
bool check_if_may_cover_scenario(const AudioSetConfigurations* audio_set_confs,
diff --git a/system/bta/le_audio/le_audio_types.h b/system/bta/le_audio/le_audio_types.h
index 67dbeb4..b5935a5 100644
--- a/system/bta/le_audio/le_audio_types.h
+++ b/system/bta/le_audio/le_audio_types.h
@@ -751,11 +751,12 @@
codec_spec_conf::kLeAudioLocationFrontRight;
/* Declarations */
-void get_cis_count(const AudioSetConfigurations* audio_set_configurations,
+void get_cis_count(const AudioSetConfigurations& audio_set_configurations,
+ int expected_device_cnt,
types::LeAudioConfigurationStrategy strategy,
int group_ase_snk_cnt, int group_ase_src_count,
- uint8_t* cis_count_bidir, uint8_t* cis_count_unidir_sink,
- uint8_t* cis_count_unidir_source);
+ uint8_t& cis_count_bidir, uint8_t& cis_count_unidir_sink,
+ uint8_t& cis_count_unidir_source);
bool check_if_may_cover_scenario(
const AudioSetConfigurations* audio_set_configurations, uint8_t group_size);
bool check_if_may_cover_scenario(
diff --git a/system/bta/le_audio/state_machine.cc b/system/bta/le_audio/state_machine.cc
index 6384274..8476fec 100644
--- a/system/bta/le_audio/state_machine.cc
+++ b/system/bta/le_audio/state_machine.cc
@@ -214,7 +214,8 @@
case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING: {
/* This case just updates the metadata for the stream, in case
- * stream configuration is satisfied
+ * stream configuration is satisfied. We can do that already for
+ * all the devices in a group, without any state transitions.
*/
if (!group->IsMetadataChanged(metadata_context_type, ccid_list))
return true;
@@ -225,8 +226,11 @@
return false;
}
- PrepareAndSendUpdateMetadata(group, leAudioDevice,
- metadata_context_type, ccid_list);
+ while (leAudioDevice) {
+ PrepareAndSendUpdateMetadata(group, leAudioDevice,
+ metadata_context_type, ccid_list);
+ leAudioDevice = group->GetNextActiveDevice(leAudioDevice);
+ }
break;
}
diff --git a/system/bta/le_audio/state_machine_test.cc b/system/bta/le_audio/state_machine_test.cc
index da84810..2d0fc44 100644
--- a/system/bta/le_audio/state_machine_test.cc
+++ b/system/bta/le_audio/state_machine_test.cc
@@ -32,6 +32,7 @@
#include "le_audio_set_configuration_provider.h"
#include "mock_codec_manager.h"
#include "mock_controller.h"
+#include "mock_csis_client.h"
#include "mock_iso_manager.h"
#include "types/bt_transport.h"
@@ -53,6 +54,10 @@
extern struct fake_osi_alarm_set_on_mloop fake_osi_alarm_set_on_mloop_;
void osi_property_set_bool(const char* key, bool value);
+static const char* test_flags[] = {
+ "INIT_logging_debug_enabled_for_all=true",
+ nullptr,
+};
constexpr uint8_t media_ccid = 0xC0;
constexpr auto media_context =
@@ -77,6 +82,8 @@
static_cast<LeAudioContextType>(0x0002);
constexpr LeAudioContextType kContextTypeMedia =
static_cast<LeAudioContextType>(0x0004);
+constexpr LeAudioContextType kContextTypeSoundEffects =
+ static_cast<LeAudioContextType>(0x0080);
constexpr LeAudioContextType kContextTypeRingtone =
static_cast<LeAudioContextType>(0x0200);
@@ -146,11 +153,6 @@
return {{0xC0, 0xDE, 0xC0, 0xDE, 0x00, index}};
}
-static uint8_t ase_id_last_assigned;
-static uint8_t additional_ases = 0;
-static uint8_t channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel;
-static uint16_t sample_freq_ = codec_specific::kCapSamplingFrequency16000Hz;
-
class MockLeAudioGroupStateMachineCallbacks
: public LeAudioGroupStateMachine::Callbacks {
public:
@@ -169,20 +171,36 @@
class StateMachineTest : public Test {
protected:
+ uint8_t ase_id_last_assigned = types::ase::kAseIdInvalid;
+ uint8_t additional_snk_ases = 0;
+ uint8_t additional_src_ases = 0;
+ uint8_t channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel;
+ uint16_t sample_freq_ = codec_specific::kCapSamplingFrequency16000Hz;
+
void SetUp() override {
+ bluetooth::common::InitFlags::Load(test_flags);
mock_function_count_map.clear();
controller::SetMockControllerInterface(&mock_controller_);
bluetooth::manager::SetMockBtmInterface(&btm_interface);
gatt::SetMockBtaGattInterface(&gatt_interface);
gatt::SetMockBtaGattQueue(&gatt_queue);
- ase_id_last_assigned = types::ase::kAseIdInvalid;
- additional_ases = 0;
::le_audio::AudioSetConfigurationProvider::Initialize();
LeAudioGroupStateMachine::Initialize(&mock_callbacks_);
ContentControlIdKeeper::GetInstance()->Start();
+ MockCsisClient::SetMockInstanceForTesting(&mock_csis_client_module_);
+ ON_CALL(mock_csis_client_module_, Get())
+ .WillByDefault(Return(&mock_csis_client_module_));
+ ON_CALL(mock_csis_client_module_, IsCsisClientRunning())
+ .WillByDefault(Return(true));
+ ON_CALL(mock_csis_client_module_, GetDeviceList(_))
+ .WillByDefault(Invoke([this](int group_id) { return addresses_; }));
+ ON_CALL(mock_csis_client_module_, GetDesiredSize(_))
+ .WillByDefault(
+ Invoke([this](int group_id) { return (int)(addresses_.size()); }));
+
// Support 2M Phy
ON_CALL(mock_controller_, SupportsBle2mPhy()).WillByDefault(Return(true));
ON_CALL(btm_interface, IsPhy2mSupported(_, _)).WillByDefault(Return(true));
@@ -445,6 +463,7 @@
ase_ctp_handlers[i] = nullptr;
le_audio_devices_.clear();
+ addresses_.clear();
cached_codec_configuration_map_.clear();
cached_ase_to_cis_id_map_.clear();
LeAudioGroupStateMachine::Cleanup();
@@ -492,6 +511,7 @@
}
le_audio_devices_.push_back(leAudioDevice);
+ addresses_.push_back(leAudioDevice->address_);
return std::move(leAudioDevice);
}
@@ -511,10 +531,9 @@
return &(*group);
}
- static void InjectAseStateNotification(types::ase* ase, LeAudioDevice* device,
- LeAudioDeviceGroup* group,
- uint8_t new_state,
- void* new_state_params) {
+ void InjectAseStateNotification(types::ase* ase, LeAudioDevice* device,
+ LeAudioDeviceGroup* group, uint8_t new_state,
+ void* new_state_params) {
// Prepare additional params
switch (new_state) {
case ascs::kAseStateCodecConfigured: {
@@ -661,7 +680,7 @@
});
}
- static void InjectInitialIdleNotification(LeAudioDeviceGroup* group) {
+ void InjectInitialIdleNotification(LeAudioDeviceGroup* group) {
for (auto* device = group->GetFirstDevice(); device != nullptr;
device = group->GetNextDevice(device)) {
for (auto& ase : device->ases_) {
@@ -686,18 +705,18 @@
uint8_t num_ase_src;
switch (context_type) {
case kContextTypeRingtone:
- num_ase_snk = 1 + additional_ases;
- num_ase_src = 0;
+ num_ase_snk = 1 + additional_snk_ases;
+ num_ase_src = 0 + additional_src_ases;
break;
case kContextTypeMedia:
- num_ase_snk = 2 + additional_ases;
- num_ase_src = 0;
+ num_ase_snk = 2 + additional_snk_ases;
+ num_ase_src = 0 + additional_src_ases;
break;
case kContextTypeConversational:
- num_ase_snk = 1 + additional_ases;
- num_ase_src = 1;
+ num_ase_snk = 1 + additional_snk_ases;
+ num_ase_src = 1 + additional_src_ases;
break;
default:
@@ -941,7 +960,7 @@
void PrepareEnableHandler(LeAudioDeviceGroup* group, int verify_ase_count = 0,
bool inject_enabling = true) {
ase_ctp_handlers[ascs::kAseCtpOpcodeEnable] =
- [group, verify_ase_count, inject_enabling](
+ [group, verify_ase_count, inject_enabling, this](
LeAudioDevice* device, std::vector<uint8_t> value,
GATT_WRITE_OP_CB cb, void* cb_data) {
auto num_ase = value[1];
@@ -989,9 +1008,9 @@
void PrepareDisableHandler(LeAudioDeviceGroup* group,
int verify_ase_count = 0) {
ase_ctp_handlers[ascs::kAseCtpOpcodeDisable] =
- [group, verify_ase_count](LeAudioDevice* device,
- std::vector<uint8_t> value,
- GATT_WRITE_OP_CB cb, void* cb_data) {
+ [group, verify_ase_count, this](LeAudioDevice* device,
+ std::vector<uint8_t> value,
+ GATT_WRITE_OP_CB cb, void* cb_data) {
auto num_ase = value[1];
// Verify ase count if needed
@@ -1035,9 +1054,9 @@
void PrepareReceiverStartReady(LeAudioDeviceGroup* group,
int verify_ase_count = 0) {
ase_ctp_handlers[ascs::kAseCtpOpcodeReceiverStartReady] =
- [group, verify_ase_count](LeAudioDevice* device,
- std::vector<uint8_t> value,
- GATT_WRITE_OP_CB cb, void* cb_data) {
+ [group, verify_ase_count, this](LeAudioDevice* device,
+ std::vector<uint8_t> value,
+ GATT_WRITE_OP_CB cb, void* cb_data) {
auto num_ase = value[1];
// Verify ase count if needed
@@ -1073,9 +1092,9 @@
void PrepareReceiverStopReady(LeAudioDeviceGroup* group,
int verify_ase_count = 0) {
ase_ctp_handlers[ascs::kAseCtpOpcodeReceiverStopReady] =
- [group, verify_ase_count](LeAudioDevice* device,
- std::vector<uint8_t> value,
- GATT_WRITE_OP_CB cb, void* cb_data) {
+ [group, verify_ase_count, this](LeAudioDevice* device,
+ std::vector<uint8_t> value,
+ GATT_WRITE_OP_CB cb, void* cb_data) {
auto num_ase = value[1];
// Verify ase count if needed
@@ -1143,6 +1162,7 @@
};
}
+ MockCsisClient mock_csis_client_module_;
NiceMock<controller::MockControllerInterface> mock_controller_;
NiceMock<bluetooth::manager::MockBtmInterface> btm_interface;
gatt::MockBtaGattInterface gatt_interface;
@@ -1163,6 +1183,7 @@
MockLeAudioGroupStateMachineCallbacks mock_callbacks_;
std::vector<std::shared_ptr<LeAudioDevice>> le_audio_devices_;
+ std::vector<RawAddress> addresses_;
std::map<uint8_t, std::unique_ptr<LeAudioDeviceGroup>>
le_audio_device_groups_;
bool group_create_command_disallowed_ = false;
@@ -1179,6 +1200,9 @@
}
TEST_F(StateMachineTest, testConfigureCodecSingle) {
+ /* Device is banded headphones with 1x snk + 0x src ase
+ * (1xunidirectional CIS) with channel count 2 (for stereo
+ */
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 2;
channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
@@ -1259,6 +1283,11 @@
}
TEST_F(StateMachineTest, testConfigureQosSingle) {
+ /* Device is banded headphones with 2x snk + 1x src ase
+ * (1x bidirectional + 1xunidirectional CIS)
+ */
+ additional_snk_ases = 1;
+ additional_src_ases = 1;
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 3;
@@ -1269,8 +1298,8 @@
* should have been configured.
*/
auto* leAudioDevice = group->GetFirstDevice();
- PrepareConfigureCodecHandler(group, 1);
- PrepareConfigureQosHandler(group, 1);
+ PrepareConfigureCodecHandler(group, 2);
+ PrepareConfigureQosHandler(group, 2);
// Start the configuration and stream Media content
EXPECT_CALL(gatt_queue,
@@ -1296,6 +1325,11 @@
}
TEST_F(StateMachineTest, testConfigureQosSingleRecoverCig) {
+ /* Device is banded headphones with 2x snk + 1x src ase
+ * (1x bidirectional + 1xunidirectional CIS)
+ */
+ additional_snk_ases = 1;
+ additional_src_ases = 1;
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 3;
@@ -1309,8 +1343,8 @@
* should have been configured.
*/
auto* leAudioDevice = group->GetFirstDevice();
- PrepareConfigureCodecHandler(group, 1);
- PrepareConfigureQosHandler(group, 1);
+ PrepareConfigureCodecHandler(group, 2);
+ PrepareConfigureQosHandler(group, 2);
// Start the configuration and stream Media content
EXPECT_CALL(gatt_queue,
@@ -1382,14 +1416,19 @@
}
TEST_F(StateMachineTest, testStreamSingle) {
+ /* Device is banded headphones with 1x snk + 0x src ase
+ * (1xunidirectional CIS) with channel count 2 (for stereo
+ */
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 4;
+ channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
+ kLeAudioCodecLC3ChannelCountTwoChannel;
// Prepare fake connected device group
auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
- /* Since we prepared device with Ringtone context in mind, only one ASE
- * should have been configured.
+ /* Ringtone with channel count 1 for single device and 1 ASE sink will
+ * end up with 1 Sink ASE being configured.
*/
PrepareConfigureCodecHandler(group, 1);
PrepareConfigureQosHandler(group, 1);
@@ -1427,18 +1466,21 @@
}
TEST_F(StateMachineTest, testStreamSkipEnablingSink) {
- const auto context_type = kContextTypeRingtone;
+ /* Device is banded headphones with 2x snk + none src ase
+ * (2x unidirectional CIS)
+ */
+ const auto context_type = kContextTypeMedia;
const int leaudio_group_id = 4;
// Prepare fake connected device group
auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
- /* Since we prepared device with Ringtone context in mind, only one ASE
- * should have been configured.
+ /* For Media context type with channel count 1 and two ASEs,
+ * there should have be 2 Ases configured configured.
*/
- PrepareConfigureCodecHandler(group, 1);
- PrepareConfigureQosHandler(group, 1);
- PrepareEnableHandler(group, 1, false);
+ PrepareConfigureCodecHandler(group, 2);
+ PrepareConfigureQosHandler(group, 2);
+ PrepareEnableHandler(group, 2, false);
auto* leAudioDevice = group->GetFirstDevice();
EXPECT_CALL(gatt_queue,
@@ -1448,7 +1490,7 @@
EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
- EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
@@ -1472,18 +1514,23 @@
}
TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) {
+ /* Device is banded headphones with 2x snk + 1x src ase
+ * (1x bidirectional CIS)
+ */
const auto context_type = kContextTypeConversational;
const int leaudio_group_id = 4;
+ additional_snk_ases = 1;
+
// Prepare fake connected device group
auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
- /* Since we prepared device with Conversional context in mind, one Sink ASE
- * and one Source ASE should have been configured.
+ /* Since we prepared device with Conversional context in mind,
+ * 2 Sink ASEs and 1 Source ASE should have been configured.
*/
- PrepareConfigureCodecHandler(group, 2);
- PrepareConfigureQosHandler(group, 2);
- PrepareEnableHandler(group, 2, false);
+ PrepareConfigureCodecHandler(group, 3);
+ PrepareConfigureQosHandler(group, 3);
+ PrepareEnableHandler(group, 3, false);
PrepareReceiverStartReady(group, 1);
auto* leAudioDevice = group->GetFirstDevice();
@@ -1494,7 +1541,7 @@
EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
- EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+ EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(3);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
@@ -1622,20 +1669,98 @@
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
}
+TEST_F(StateMachineTest, testUpdateMetadataMultiple) {
+ const auto context_type = kContextTypeMedia;
+ const auto leaudio_group_id = 4;
+ const auto num_devices = 2;
+
+ // Prepare multiple fake connected devices in a group
+ auto* group =
+ PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+ ASSERT_EQ(group->Size(), num_devices);
+
+ PrepareConfigureCodecHandler(group);
+ PrepareConfigureQosHandler(group);
+ PrepareEnableHandler(group);
+
+ EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(AtLeast(1));
+ EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+ EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
+ EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
+
+ InjectInitialIdleNotification(group);
+
+ auto* leAudioDevice = group->GetFirstDevice();
+ auto expected_devices_written = 0;
+ while (leAudioDevice) {
+ EXPECT_CALL(gatt_queue,
+ WriteCharacteristic(leAudioDevice->conn_id_,
+ leAudioDevice->ctp_hdls_.val_hdl, _,
+ GATT_WRITE_NO_RSP, _, _))
+ .Times(AtLeast(3));
+ expected_devices_written++;
+ leAudioDevice = group->GetNextDevice(leAudioDevice);
+ }
+ ASSERT_EQ(expected_devices_written, num_devices);
+
+ // Validate GroupStreamStatus
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::STREAMING));
+
+ // Start the configuration and stream Media content
+ ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
+ group, static_cast<LeAudioContextType>(context_type),
+ types::AudioContexts(context_type)));
+
+ testing::Mock::VerifyAndClearExpectations(&gatt_queue);
+
+ // Check if group has transitioned to a proper state
+ ASSERT_EQ(group->GetState(),
+ types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
+ // Make sure all devices get the metadata update
+ leAudioDevice = group->GetFirstDevice();
+ expected_devices_written = 0;
+ while (leAudioDevice) {
+ EXPECT_CALL(gatt_queue,
+ WriteCharacteristic(leAudioDevice->conn_id_,
+ leAudioDevice->ctp_hdls_.val_hdl, _,
+ GATT_WRITE_NO_RSP, _, _))
+ .Times(1);
+ expected_devices_written++;
+ leAudioDevice = group->GetNextDevice(leAudioDevice);
+ }
+ ASSERT_EQ(expected_devices_written, num_devices);
+
+ const auto metadata_context_type =
+ kContextTypeMedia | kContextTypeSoundEffects;
+ ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
+ group, static_cast<LeAudioContextType>(context_type),
+ metadata_context_type));
+}
+
TEST_F(StateMachineTest, testDisableSingle) {
+ /* Device is banded headphones with 2x snk + 0x src ase
+ * (2xunidirectional CIS)
+ */
+ additional_snk_ases = 1;
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 4;
// Prepare fake connected device group
auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
- /* Since we prepared device with Ringtone context in mind, only one ASE
- * should have been configured.
+ /* Ringtone context plus additional ASE with channel count 1
+ * gives us 2 ASE which should have been configured.
*/
- PrepareConfigureCodecHandler(group, 1);
- PrepareConfigureQosHandler(group, 1);
- PrepareEnableHandler(group, 1);
- PrepareDisableHandler(group, 1);
+ PrepareConfigureCodecHandler(group, 2);
+ PrepareConfigureQosHandler(group, 2);
+ PrepareEnableHandler(group, 2);
+ PrepareDisableHandler(group, 2);
auto* leAudioDevice = group->GetFirstDevice();
EXPECT_CALL(gatt_queue,
@@ -1645,13 +1770,13 @@
EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
- EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
EXPECT_CALL(
*mock_iso_manager_,
RemoveIsoDataPath(
_, bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionInput))
- .Times(1);
- EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
+ .Times(2);
+ EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2);
EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
InjectInitialIdleNotification(group);
@@ -1752,6 +1877,10 @@
}
TEST_F(StateMachineTest, testDisableBidirectional) {
+ /* Device is banded headphones with 2x snk + 1x src ase
+ * (1x bidirectional + 1xunidirectional CIS)
+ */
+ additional_snk_ases = 1;
const auto context_type = kContextTypeConversational;
const int leaudio_group_id = 4;
@@ -1761,10 +1890,10 @@
/* Since we prepared device with Conversional context in mind, Sink and Source
* ASEs should have been configured.
*/
- PrepareConfigureCodecHandler(group, 2);
- PrepareConfigureQosHandler(group, 2);
- PrepareEnableHandler(group, 2);
- PrepareDisableHandler(group, 2);
+ PrepareConfigureCodecHandler(group, 3);
+ PrepareConfigureQosHandler(group, 3);
+ PrepareEnableHandler(group, 3);
+ PrepareDisableHandler(group, 3);
PrepareReceiverStartReady(group, 1);
PrepareReceiverStopReady(group, 1);
@@ -1776,15 +1905,48 @@
EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
- EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
- EXPECT_CALL(
- *mock_iso_manager_,
- RemoveIsoDataPath(
- _,
- bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionInput |
- bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionOutput))
- .Times(1);
- EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(3);
+ bool removed_bidirectional = false;
+ bool removed_unidirectional = false;
+
+ /* Check data path removal */
+ ON_CALL(*mock_iso_manager_, RemoveIsoDataPath)
+ .WillByDefault(Invoke([&removed_bidirectional, &removed_unidirectional,
+ this](uint16_t conn_handle,
+ uint8_t data_path_dir) {
+ /* Set flags for verification */
+ if (data_path_dir ==
+ (bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionInput |
+ bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionOutput)) {
+ removed_bidirectional = true;
+ } else if (data_path_dir == bluetooth::hci::iso_manager::
+ kRemoveIsoDataPathDirectionInput) {
+ removed_unidirectional = true;
+ }
+
+ /* Copied from default handler of RemoveIsoDataPath*/
+ auto dev_it =
+ std::find_if(le_audio_devices_.begin(), le_audio_devices_.end(),
+ [&conn_handle](auto& dev) {
+ auto ases = dev->GetAsesByCisConnHdl(conn_handle);
+ return (ases.sink || ases.source);
+ });
+ if (dev_it == le_audio_devices_.end()) {
+ return;
+ }
+
+ for (auto& kv_pair : le_audio_device_groups_) {
+ auto& group = kv_pair.second;
+ if (group->IsDeviceInTheGroup(dev_it->get())) {
+ LeAudioGroupStateMachine::Get()->ProcessHciNotifRemoveIsoDataPath(
+ group.get(), dev_it->get(), 0, conn_handle);
+ return;
+ }
+ }
+ /* End of copy */
+ }));
+
+ EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2);
EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
// Start the configuration and stream Media content
@@ -1802,11 +1964,18 @@
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+ ASSERT_EQ(removed_bidirectional, true);
+ ASSERT_EQ(removed_unidirectional, true);
}
TEST_F(StateMachineTest, testReleaseSingle) {
+ /* Device is banded headphones with 1x snk + 0x src ase
+ * (1xunidirectional CIS) with channel count 2 (for stereo)
+ */
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 4;
+ channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
+ kLeAudioCodecLC3ChannelCountTwoChannel;
// Prepare fake connected device group
auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
@@ -1861,8 +2030,13 @@
}
TEST_F(StateMachineTest, testReleaseCachingSingle) {
+ /* Device is banded headphones with 1x snk + 0x src ase
+ * (1xunidirectional CIS)
+ */
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 4;
+ channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
+ kLeAudioCodecLC3ChannelCountTwoChannel;
// Prepare fake connected device group
auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
@@ -1931,7 +2105,7 @@
channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
kLeAudioCodecLC3ChannelCountTwoChannel;
- additional_ases = 2;
+ additional_snk_ases = 2;
// Prepare fake connected device group
auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
@@ -2017,7 +2191,7 @@
channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
kLeAudioCodecLC3ChannelCountTwoChannel;
- additional_ases = 2;
+ additional_snk_ases = 2;
/* Prepare fake connected device group with update of Media and Conversational
* contexts
*/
@@ -2175,6 +2349,10 @@
}
TEST_F(StateMachineTest, testReleaseBidirectional) {
+ /* Device is banded headphones with 2x snk + 1x src ase
+ * (1x bidirectional + 1xunidirectional CIS)
+ */
+ additional_snk_ases = 1;
const auto context_type = kContextTypeConversational;
const auto leaudio_group_id = 6;
@@ -2184,12 +2362,12 @@
/* Since we prepared device with Conversional context in mind, Sink and Source
* ASEs should have been configured.
*/
- PrepareConfigureCodecHandler(group, 2);
- PrepareConfigureQosHandler(group, 2);
- PrepareEnableHandler(group, 2);
- PrepareDisableHandler(group, 2);
+ PrepareConfigureCodecHandler(group, 3);
+ PrepareConfigureQosHandler(group, 3);
+ PrepareEnableHandler(group, 3);
+ PrepareDisableHandler(group, 3);
PrepareReceiverStartReady(group, 1);
- PrepareReleaseHandler(group, 2);
+ PrepareReleaseHandler(group, 3);
auto* leAudioDevice = group->GetFirstDevice();
EXPECT_CALL(gatt_queue,
@@ -2199,9 +2377,9 @@
EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
- EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
- EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1);
- EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(3);
+ EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(2);
+ EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2);
EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(1);
InjectInitialIdleNotification(group);
@@ -2223,6 +2401,10 @@
}
TEST_F(StateMachineTest, testDisableAndReleaseBidirectional) {
+ /* Device is banded headphones with 2x snk + 1x src ase
+ * (1x bidirectional + 1xunidirectional CIS)
+ */
+ additional_snk_ases = 1;
const auto context_type = kContextTypeConversational;
const int leaudio_group_id = 4;
@@ -2232,13 +2414,13 @@
/* Since we prepared device with Conversional context in mind, Sink and Source
* ASEs should have been configured.
*/
- PrepareConfigureCodecHandler(group, 2);
- PrepareConfigureQosHandler(group, 2);
- PrepareEnableHandler(group, 2);
- PrepareDisableHandler(group, 2);
+ PrepareConfigureCodecHandler(group, 3);
+ PrepareConfigureQosHandler(group, 3);
+ PrepareEnableHandler(group, 3);
+ PrepareDisableHandler(group, 3);
PrepareReceiverStartReady(group, 1);
PrepareReceiverStopReady(group, 1);
- PrepareReleaseHandler(group, 2);
+ PrepareReleaseHandler(group, 3);
auto* leAudioDevice = group->GetFirstDevice();
EXPECT_CALL(gatt_queue,
@@ -2248,9 +2430,9 @@
EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
- EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
- EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1);
- EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(3);
+ EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(2);
+ EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2);
EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(1);
// Start the configuration and stream Media content
@@ -2333,6 +2515,10 @@
}
TEST_F(StateMachineTest, testAseAutonomousRelease) {
+ /* Device is banded headphones with 2x snk + 1x src ase
+ * (1x bidirectional + 1xunidirectional CIS)
+ */
+ additional_snk_ases = 1;
const auto context_type = kContextTypeConversational;
const int leaudio_group_id = 4;
@@ -2342,13 +2528,13 @@
/* Since we prepared device with Conversional context in mind, Sink and Source
* ASEs should have been configured.
*/
- PrepareConfigureCodecHandler(group, 2);
- PrepareConfigureQosHandler(group, 2);
- PrepareEnableHandler(group, 2);
- PrepareDisableHandler(group, 2);
+ PrepareConfigureCodecHandler(group, 3);
+ PrepareConfigureQosHandler(group, 3);
+ PrepareEnableHandler(group, 3);
+ PrepareDisableHandler(group, 3);
PrepareReceiverStartReady(group, 1);
PrepareReceiverStopReady(group, 1);
- PrepareReleaseHandler(group, 2);
+ PrepareReleaseHandler(group, 3);
InjectInitialIdleNotification(group);
@@ -2370,7 +2556,7 @@
.Times(AtLeast(1));
/* Single disconnect as it is bidirectional Cis*/
- EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2);
for (auto* device = group->GetFirstDevice(); device != nullptr;
device = group->GetNextDevice(device)) {
@@ -2457,6 +2643,8 @@
TEST_F(StateMachineTest, testStateTransitionTimeoutOnIdleState) {
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 4;
+ channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
+ kLeAudioCodecLC3ChannelCountTwoChannel;
// Prepare fake connected device group
auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
@@ -2483,6 +2671,8 @@
TEST_F(StateMachineTest, testStateTransitionTimeout) {
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 4;
+ channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
+ kLeAudioCodecLC3ChannelCountTwoChannel;
// Prepare fake connected device group
auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
@@ -2517,6 +2707,8 @@
TEST_F(StateMachineTest, testConfigureDataPathForHost) {
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 4;
+ channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
+ kLeAudioCodecLC3ChannelCountTwoChannel;
/* Should be called 3 times because
* 1 - calling GetConfigurations just after connection
@@ -2555,6 +2747,8 @@
TEST_F(StateMachineTest, testConfigureDataPathForAdsp) {
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 4;
+ channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
+ kLeAudioCodecLC3ChannelCountTwoChannel;
/* Should be called 3 times because
* 1 - calling GetConfigurations just after connection
@@ -2959,7 +3153,8 @@
sample_freq_ |= codec_specific::kCapSamplingFrequency48000Hz |
codec_specific::kCapSamplingFrequency32000Hz;
- additional_ases = 3;
+ additional_snk_ases = 3;
+ additional_src_ases = 1;
ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
ContentControlIdKeeper::GetInstance()->SetCcid(call_context, call_ccid);
@@ -3039,5 +3234,97 @@
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
}
+
+TEST_F(StateMachineTest, BoundedHeadphonesConversationalToMediaChannelCount_1) {
+ const auto initial_context_type = kContextTypeConversational;
+ const auto new_context_type = kContextTypeMedia;
+ const auto leaudio_group_id = 6;
+ const auto num_devices = 1;
+ channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel;
+
+ sample_freq_ |= codec_specific::kCapSamplingFrequency48000Hz |
+ codec_specific::kCapSamplingFrequency32000Hz;
+ additional_snk_ases = 3;
+ additional_src_ases = 1;
+
+ ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
+ ContentControlIdKeeper::GetInstance()->SetCcid(call_context, call_ccid);
+
+ // Prepare one fake connected devices in a group
+ auto* group = PrepareSingleTestDeviceGroup(
+ leaudio_group_id, initial_context_type, num_devices,
+ kContextTypeConversational | kContextTypeMedia);
+ ASSERT_EQ(group->Size(), num_devices);
+
+ // Cannot verify here as we will change the number of ases on reconfigure
+ PrepareConfigureCodecHandler(group, 0, true);
+ PrepareConfigureQosHandler(group);
+ PrepareEnableHandler(group);
+ PrepareDisableHandler(group);
+ PrepareReleaseHandler(group);
+ PrepareReceiverStartReady(group);
+
+ InjectInitialIdleNotification(group);
+
+ auto* leAudioDevice = group->GetFirstDevice();
+ auto expected_devices_written = 0;
+ while (leAudioDevice) {
+ /* 8 Writes:
+ * 1: Codec config (+1 after reconfig)
+ * 2: Codec QoS (+1 after reconfig)
+ * 3: Enabling (+1 after reconfig)
+ * 4: ReceiverStartReady (only for conversational)
+ * 5: Release
+ */
+ EXPECT_CALL(gatt_queue,
+ WriteCharacteristic(leAudioDevice->conn_id_,
+ leAudioDevice->ctp_hdls_.val_hdl, _,
+ GATT_WRITE_NO_RSP, _, _))
+ .Times(8);
+ expected_devices_written++;
+ leAudioDevice = group->GetNextDevice(leAudioDevice);
+ }
+ ASSERT_EQ(expected_devices_written, num_devices);
+
+ // Validate GroupStreamStatus
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::STREAMING));
+
+ // Start the configuration and stream Media content
+ LeAudioGroupStateMachine::Get()->StartStream(
+ group, initial_context_type, types::AudioContexts(initial_context_type));
+
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+
+ // Validate GroupStreamStatus
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::RELEASING));
+
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(
+ leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS));
+ // Start the configuration and stream Media content
+ LeAudioGroupStateMachine::Get()->StopStream(group);
+
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+
+ // Restart stream
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::STREAMING));
+
+ // Start the configuration and stream Media content
+ LeAudioGroupStateMachine::Get()->StartStream(
+ group, new_context_type, types::AudioContexts(new_context_type));
+
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+}
} // namespace internal
} // namespace le_audio
diff --git a/system/bta/test/common/mock_csis_client.h b/system/bta/test/common/mock_csis_client.h
index 33c106a..bd28233 100644
--- a/system/bta/test/common/mock_csis_client.h
+++ b/system/bta/test/common/mock_csis_client.h
@@ -33,6 +33,7 @@
(override));
MOCK_METHOD((std::vector<RawAddress>), GetDeviceList, (int group_id),
(override));
+ MOCK_METHOD((int), GetDesiredSize, (int group_id), (override));
/* Called from static methods */
MOCK_METHOD((void), Initialize,
diff --git a/system/main/shim/l2c_api.h b/system/main/shim/l2c_api.h
index 0fe3fbd..c70b913 100644
--- a/system/main/shim/l2c_api.h
+++ b/system/main/shim/l2c_api.h
@@ -428,6 +428,8 @@
******************************************************************************/
bool L2CA_SetLeGattTimeout(const RawAddress& rem_bda, uint16_t idle_tout);
+bool L2CA_MarkLeLinkAsActive(const RawAddress& rem_bda);
+
bool L2CA_UpdateBleConnParams(const RawAddress& rem_bda, uint16_t min_int,
uint16_t max_int, uint16_t latency,
uint16_t timeout, uint16_t min_ce_len,
diff --git a/system/stack/gatt/gatt_api.cc b/system/stack/gatt/gatt_api.cc
index 0e3c23e..91bcb1e 100644
--- a/system/stack/gatt/gatt_api.cc
+++ b/system/stack/gatt/gatt_api.cc
@@ -1037,26 +1037,36 @@
*
* Parameter bd_addr: target device bd address.
* idle_tout: timeout value in seconds.
+ * transport: transport option.
+ * is_active: whether we should use this as a signal that an
+ * active client now exists (which changes link
+ * timeout logic, see
+ * t_l2c_linkcb.with_active_local_clients for
+ * details).
*
* Returns void
*
******************************************************************************/
void GATT_SetIdleTimeout(const RawAddress& bd_addr, uint16_t idle_tout,
- tBT_TRANSPORT transport) {
+ tBT_TRANSPORT transport, bool is_active) {
bool status = false;
tGATT_TCB* p_tcb = gatt_find_tcb_by_addr(bd_addr, transport);
if (p_tcb != nullptr) {
status = L2CA_SetLeGattTimeout(bd_addr, idle_tout);
+ if (is_active) {
+ status &= L2CA_MarkLeLinkAsActive(bd_addr);
+ }
+
if (idle_tout == GATT_LINK_IDLE_TIMEOUT_WHEN_NO_APP) {
L2CA_SetIdleTimeoutByBdAddr(
p_tcb->peer_bda, GATT_LINK_IDLE_TIMEOUT_WHEN_NO_APP, BT_TRANSPORT_LE);
}
}
- LOG_INFO("idle_timeout=%d, status=%d, (1-OK 0-not performed)", idle_tout,
- +status);
+ LOG_INFO("idle_timeout=%d, is_active=%d, status=%d (1-OK 0-not performed)",
+ idle_tout, is_active, +status);
}
/*******************************************************************************
diff --git a/system/stack/gatt/gatt_main.cc b/system/stack/gatt/gatt_main.cc
index 92c6a77..0550d4a 100644
--- a/system/stack/gatt/gatt_main.cc
+++ b/system/stack/gatt/gatt_main.cc
@@ -114,10 +114,11 @@
fixed_reg.pL2CA_FixedConn_Cb = gatt_le_connect_cback;
fixed_reg.pL2CA_FixedData_Cb = gatt_le_data_ind;
fixed_reg.pL2CA_FixedCong_Cb = gatt_le_cong_cback; /* congestion callback */
- fixed_reg.default_idle_tout =
- bluetooth::common::init_flags::finite_att_timeout_is_enabled()
- ? 2 /* We allow 2s for GATT clients to connect once the link is up */
- : L2CAP_NO_IDLE_TIMEOUT;
+
+ // the GATT timeout is updated after a connection
+ // is established, when we know whether any
+ // clients exist
+ fixed_reg.default_idle_tout = L2CAP_NO_IDLE_TIMEOUT;
L2CA_RegisterFixedChannel(L2CAP_ATT_CID, &fixed_reg);
@@ -376,7 +377,7 @@
p_tcb->peer_bda.ToString().c_str());
/* acl link is connected disable the idle timeout */
GATT_SetIdleTimeout(p_tcb->peer_bda, GATT_LINK_NO_IDLE_TIMEOUT,
- p_tcb->transport);
+ p_tcb->transport, true /* is_active */);
} else {
LOG_INFO("invalid handle %d or dynamic CID %d", is_valid_handle,
p_tcb->att_lcid);
@@ -395,7 +396,7 @@
"%d seconds",
GATT_LINK_IDLE_TIMEOUT_WHEN_NO_APP);
GATT_SetIdleTimeout(p_tcb->peer_bda, GATT_LINK_IDLE_TIMEOUT_WHEN_NO_APP,
- p_tcb->transport);
+ p_tcb->transport, false /* is_active */);
} else {
// disconnect the dynamic channel
LOG_INFO("disconnect GATT dynamic channel");
@@ -819,11 +820,18 @@
/* Remove the direct connection */
connection_manager::on_connection_complete(p_tcb->peer_bda);
- if (!p_tcb->app_hold_link.empty() && p_tcb->att_lcid == L2CAP_ATT_CID) {
- /* disable idle timeout if one or more clients are holding the link disable
- * the idle timer */
- GATT_SetIdleTimeout(p_tcb->peer_bda, GATT_LINK_NO_IDLE_TIMEOUT,
- p_tcb->transport);
+ if (p_tcb->att_lcid == L2CAP_ATT_CID) {
+ if (!p_tcb->app_hold_link.empty()) {
+ /* disable idle timeout if one or more clients are holding the link
+ * disable the idle timer */
+ GATT_SetIdleTimeout(p_tcb->peer_bda, GATT_LINK_NO_IDLE_TIMEOUT,
+ p_tcb->transport, true /* is_active */);
+ } else {
+ if (bluetooth::common::init_flags::finite_att_timeout_is_enabled()) {
+ GATT_SetIdleTimeout(p_tcb->peer_bda, GATT_LINK_IDLE_TIMEOUT_WHEN_NO_APP,
+ p_tcb->transport, false /* is_active */);
+ }
+ }
}
}
diff --git a/system/stack/include/gatt_api.h b/system/stack/include/gatt_api.h
index 1bb3d07..4486771 100644
--- a/system/stack/include/gatt_api.h
+++ b/system/stack/include/gatt_api.h
@@ -999,13 +999,18 @@
*
* Parameter bd_addr: target device bd address.
* idle_tout: timeout value in seconds.
- * transport: trasnport option.
+ * transport: transport option.
+ * is_active: whether we should use this as a signal that an
+ * active client now exists (which changes link
+ * timeout logic, see
+ * t_l2c_linkcb.with_active_local_clients for
+ * details).
*
* Returns void
*
******************************************************************************/
extern void GATT_SetIdleTimeout(const RawAddress& bd_addr, uint16_t idle_tout,
- tBT_TRANSPORT transport);
+ tBT_TRANSPORT transport, bool is_active);
/*******************************************************************************
*
diff --git a/system/stack/include/l2c_api.h b/system/stack/include/l2c_api.h
index 42688d2..d62da3c 100644
--- a/system/stack/include/l2c_api.h
+++ b/system/stack/include/l2c_api.h
@@ -842,6 +842,8 @@
extern bool L2CA_SetLeGattTimeout(const RawAddress& rem_bda,
uint16_t idle_tout);
+extern bool L2CA_MarkLeLinkAsActive(const RawAddress& rem_bda);
+
extern bool L2CA_UpdateBleConnParams(const RawAddress& rem_bda,
uint16_t min_int, uint16_t max_int,
uint16_t latency, uint16_t timeout,
diff --git a/system/stack/l2cap/l2c_api.cc b/system/stack/l2cap/l2c_api.cc
index 492a379..5464cb4 100644
--- a/system/stack/l2cap/l2c_api.cc
+++ b/system/stack/l2cap/l2c_api.cc
@@ -1558,6 +1558,16 @@
return true;
}
+bool L2CA_MarkLeLinkAsActive(const RawAddress& rem_bda) {
+ tL2C_LCB* p_lcb = l2cu_find_lcb_by_bd_addr(rem_bda, BT_TRANSPORT_LE);
+ if (p_lcb == NULL) {
+ return false;
+ }
+ LOG(INFO) << __func__ << "setting link to " << rem_bda << " as active";
+ p_lcb->with_active_local_clients = true;
+ return true;
+}
+
/*******************************************************************************
*
* Function L2CA_DataWrite
diff --git a/system/stack/l2cap/l2c_int.h b/system/stack/l2cap/l2c_int.h
index ac02e95..a5ba258 100644
--- a/system/stack/l2cap/l2c_int.h
+++ b/system/stack/l2cap/l2c_int.h
@@ -428,6 +428,13 @@
tL2C_LINK_STATE link_state;
alarm_t* l2c_lcb_timer; /* Timer entry for timeout evt */
+
+ // This tracks if the link has ever either (a)
+ // been used for a dynamic channel (EATT or L2CAP CoC), or (b) has been a
+ // GATT client. If false, the local device is just a GATT server, so for
+ // backwards compatibility we never do a link timeout.
+ bool with_active_local_clients{false};
+
private:
uint16_t handle_; /* The handle used with LM */
friend void l2cu_set_lcb_handle(struct t_l2c_linkcb& p_lcb, uint16_t handle);
diff --git a/system/stack/l2cap/l2c_utils.cc b/system/stack/l2cap/l2c_utils.cc
index 6ffd13c..d903905 100755
--- a/system/stack/l2cap/l2c_utils.cc
+++ b/system/stack/l2cap/l2c_utils.cc
@@ -68,6 +68,7 @@
p_lcb->remote_bd_addr = p_bd_addr;
p_lcb->in_use = true;
+ p_lcb->with_active_local_clients = false;
p_lcb->link_state = LST_DISCONNECTED;
p_lcb->InvalidateHandle();
p_lcb->l2c_lcb_timer = alarm_new("l2c_lcb.l2c_lcb_timer");
@@ -1350,7 +1351,7 @@
*
******************************************************************************/
tL2C_CCB* l2cu_allocate_ccb(tL2C_LCB* p_lcb, uint16_t cid) {
- LOG_DEBUG("cid 0x%04x", cid);
+ LOG_DEBUG("is_dynamic = %d, cid 0x%04x", p_lcb != nullptr, cid);
if (!l2cb.p_free_ccb_first) {
LOG_ERROR("First free ccb is null for cid 0x%04x", cid);
return nullptr;
@@ -1467,6 +1468,11 @@
l2c_link_adjust_chnl_allocation();
+ if (p_lcb != NULL) {
+ // once a dynamic channel is opened, timeouts become active
+ p_lcb->with_active_local_clients = true;
+ }
+
return p_ccb;
}
@@ -2605,11 +2611,11 @@
for (xx = 0; xx < L2CAP_NUM_FIXED_CHNLS; xx++) {
if ((p_lcb->p_fixed_ccbs[xx] != NULL) &&
(p_lcb->p_fixed_ccbs[xx]->fixed_chnl_idle_tout * 1000 > timeout_ms)) {
-
- if (p_lcb->p_fixed_ccbs[xx]->fixed_chnl_idle_tout == L2CAP_NO_IDLE_TIMEOUT) {
- L2CAP_TRACE_DEBUG("%s NO IDLE timeout set for fixed cid 0x%04x", __func__,
- p_lcb->p_fixed_ccbs[xx]->local_cid);
- start_timeout = false;
+ if (p_lcb->p_fixed_ccbs[xx]->fixed_chnl_idle_tout ==
+ L2CAP_NO_IDLE_TIMEOUT) {
+ L2CAP_TRACE_DEBUG("%s NO IDLE timeout set for fixed cid 0x%04x",
+ __func__, p_lcb->p_fixed_ccbs[xx]->local_cid);
+ start_timeout = false;
}
timeout_ms = p_lcb->p_fixed_ccbs[xx]->fixed_chnl_idle_tout * 1000;
}
@@ -2618,6 +2624,24 @@
/* If the link is pairing, do not mess with the timeouts */
if (p_lcb->IsBonding()) return;
+ L2CAP_TRACE_DEBUG("l2cu_no_dynamic_ccbs() with_active_local_clients=%d",
+ p_lcb->with_active_local_clients);
+ // Inactive connections should not timeout, since the ATT channel might still
+ // be in use even without a GATT client. We only timeout if either a dynamic
+ // channel or a GATT client was used, since then we expect the client to
+ // manage the lifecycle of the connection.
+
+ // FOR T ONLY: We add the outer safety-check to only do this for LE/ATT, to
+ // minimize behavioral changes outside a dessert release. But for consistency
+ // this should happen throughout on U (i.e. for classic transport + other
+ // fixed channels too)
+ if (p_lcb->p_fixed_ccbs[L2CAP_ATT_CID - L2CAP_FIRST_FIXED_CHNL] != NULL) {
+ if (bluetooth::common::init_flags::finite_att_timeout_is_enabled() &&
+ !p_lcb->with_active_local_clients) {
+ return;
+ }
+ }
+
if (timeout_ms == 0) {
L2CAP_TRACE_DEBUG(
"l2cu_no_dynamic_ccbs() IDLE timer 0, disconnecting link");
diff --git a/system/test/mock/mock_stack_gatt_api.cc b/system/test/mock/mock_stack_gatt_api.cc
index 2c40b7c..5a29c42 100644
--- a/system/test/mock/mock_stack_gatt_api.cc
+++ b/system/test/mock/mock_stack_gatt_api.cc
@@ -207,10 +207,10 @@
eatt_support);
}
void GATT_SetIdleTimeout(const RawAddress& bd_addr, uint16_t idle_tout,
- tBT_TRANSPORT transport) {
+ tBT_TRANSPORT transport, bool is_active) {
mock_function_count_map[__func__]++;
- test::mock::stack_gatt_api::GATT_SetIdleTimeout(bd_addr, idle_tout,
- transport);
+ test::mock::stack_gatt_api::GATT_SetIdleTimeout(bd_addr, idle_tout, transport,
+ is_active);
}
void GATT_StartIf(tGATT_IF gatt_if) {
mock_function_count_map[__func__]++;
diff --git a/system/test/mock/mock_stack_gatt_api.h b/system/test/mock/mock_stack_gatt_api.h
index 2e8a473..0c34693 100644
--- a/system/test/mock/mock_stack_gatt_api.h
+++ b/system/test/mock/mock_stack_gatt_api.h
@@ -365,12 +365,12 @@
// transport Return: void
struct GATT_SetIdleTimeout {
std::function<void(const RawAddress& bd_addr, uint16_t idle_tout,
- tBT_TRANSPORT transport)>
+ tBT_TRANSPORT transport, bool is_active)>
body{[](const RawAddress& bd_addr, uint16_t idle_tout,
- tBT_TRANSPORT transport) {}};
+ tBT_TRANSPORT transport, bool is_active) {}};
void operator()(const RawAddress& bd_addr, uint16_t idle_tout,
- tBT_TRANSPORT transport) {
- body(bd_addr, idle_tout, transport);
+ tBT_TRANSPORT transport, bool is_active) {
+ body(bd_addr, idle_tout, transport, is_active);
};
};
extern struct GATT_SetIdleTimeout GATT_SetIdleTimeout;
diff --git a/system/test/mock/mock_stack_l2cap_api.cc b/system/test/mock/mock_stack_l2cap_api.cc
index 450753c..27cd1e3 100644
--- a/system/test/mock/mock_stack_l2cap_api.cc
+++ b/system/test/mock/mock_stack_l2cap_api.cc
@@ -93,6 +93,7 @@
struct L2CA_SendFixedChnlData L2CA_SendFixedChnlData;
struct L2CA_RemoveFixedChnl L2CA_RemoveFixedChnl;
struct L2CA_SetLeGattTimeout L2CA_SetLeGattTimeout;
+struct L2CA_MarkLeLinkAsActive L2CA_MarkLeLinkAsActive;
struct L2CA_DataWrite L2CA_DataWrite;
struct L2CA_LECocDataWrite L2CA_LECocDataWrite;
struct L2CA_SetChnlFlushability L2CA_SetChnlFlushability;
@@ -259,6 +260,10 @@
mock_function_count_map[__func__]++;
return test::mock::stack_l2cap_api::L2CA_SetLeGattTimeout(rem_bda, idle_tout);
}
+bool L2CA_MarkLeLinkAsActive(const RawAddress& rem_bda) {
+ mock_function_count_map[__func__]++;
+ return test::mock::stack_l2cap_api::L2CA_MarkLeLinkAsActive(rem_bda);
+}
uint8_t L2CA_DataWrite(uint16_t cid, BT_HDR* p_data) {
mock_function_count_map[__func__]++;
return test::mock::stack_l2cap_api::L2CA_DataWrite(cid, p_data);
diff --git a/system/test/mock/mock_stack_l2cap_api.h b/system/test/mock/mock_stack_l2cap_api.h
index fb0ef55..4488101 100644
--- a/system/test/mock/mock_stack_l2cap_api.h
+++ b/system/test/mock/mock_stack_l2cap_api.h
@@ -421,6 +421,15 @@
};
};
extern struct L2CA_SetLeGattTimeout L2CA_SetLeGattTimeout;
+// Name: L2CA_MarkLeLinkAsActive
+// Params: const RawAddress& rem_bda
+// Returns: bool
+struct L2CA_MarkLeLinkAsActive {
+ std::function<bool(const RawAddress& rem_bda)> body{
+ [](const RawAddress& rem_bda) { return false; }};
+ bool operator()(const RawAddress& rem_bda) { return body(rem_bda); };
+};
+extern struct L2CA_MarkLeLinkAsActive L2CA_MarkLeLinkAsActive;
// Name: L2CA_DataWrite
// Params: uint16_t cid, BT_HDR* p_data
// Returns: uint8_t