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 1e577b3734fb8562f678bd395392f76ce2620ee1..36aa3b94aa93b5160563761ecd65ba0b90c980db 100644 --- a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java @@ -87,7 +87,6 @@ import com.android.bluetooth.mcp.McpService; import com.android.bluetooth.tbs.TbsGatt; import com.android.bluetooth.tbs.TbsService; import com.android.bluetooth.vc.VolumeControlService; -import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.SynchronousResultReceiver; @@ -98,7 +97,9 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.Collectors; /** @@ -145,6 +146,9 @@ public class LeAudioService extends ProfileService { private BluetoothDevice mExposedActiveDevice; private LeAudioCodecConfig mLeAudioCodecConfig; private final ReentrantLock mGroupLock = new ReentrantLock(); + private final ReentrantReadWriteLock mGroupReadWriteLock = new ReentrantReadWriteLock(); + private final Lock mGroupReadLock = mGroupReadWriteLock.readLock(); + private final Lock mGroupWriteLock = mGroupReadWriteLock.writeLock(); private FeatureFlags mFeatureFlags; ServiceFactory mServiceFactory = new ServiceFactory(); @@ -270,7 +274,6 @@ public class LeAudioService extends ProfileService { List<BluetoothLeAudioCodecConfig> mInputLocalCodecCapabilities = new ArrayList<>(); List<BluetoothLeAudioCodecConfig> mOutputLocalCodecCapabilities = new ArrayList<>(); - @GuardedBy("mGroupLock") private final Map<Integer, LeAudioGroupDescriptor> mGroupDescriptors = new LinkedHashMap<>(); private final Map<BluetoothDevice, LeAudioDeviceDescriptor> mDeviceDescriptors = new LinkedHashMap<>(); @@ -340,12 +343,12 @@ public class LeAudioService extends ProfileService { mBroadcastDescriptors.clear(); - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ false); try { mDeviceDescriptors.clear(); mGroupDescriptors.clear(); } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ false); } // Setup broadcast callbacks @@ -434,33 +437,46 @@ public class LeAudioService extends ProfileService { // Don't wait for async call with INACTIVE group status, clean active // device for active group. - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { - for (Map.Entry<Integer, LeAudioGroupDescriptor> entry : mGroupDescriptors.entrySet()) { - LeAudioGroupDescriptor descriptor = entry.getValue(); - Integer group_id = entry.getKey(); - if (descriptor.mIsActive) { - descriptor.mIsActive = false; - updateActiveDevices(group_id, descriptor.mDirection, AUDIO_DIRECTION_NONE, - descriptor.mIsActive, false, false); - break; + try { + for (Map.Entry<Integer, LeAudioGroupDescriptor> entry : + mGroupDescriptors.entrySet()) { + LeAudioGroupDescriptor descriptor = entry.getValue(); + Integer group_id = entry.getKey(); + if (descriptor.mIsActive) { + descriptor.mIsActive = false; + updateActiveDevices( + group_id, + descriptor.mDirection, + AUDIO_DIRECTION_NONE, + descriptor.mIsActive, + false, + false); + break; + } } - } - // Destroy state machines and stop handler thread - for (LeAudioDeviceDescriptor descriptor : mDeviceDescriptors.values()) { - LeAudioStateMachine sm = descriptor.mStateMachine; - if (sm == null) { - continue; + // Destroy state machines and stop handler thread + for (LeAudioDeviceDescriptor descriptor : mDeviceDescriptors.values()) { + LeAudioStateMachine sm = descriptor.mStateMachine; + if (sm == null) { + continue; + } + sm.quit(); + sm.cleanup(); + } + } finally { + if (mFeatureFlags.leaudioApiSynchronizedBlockFix()) { + // Upgrade to write lock + groupMutexUnlock(/* isReadOnly */ true); + groupMutexLock(/* isReadOnly */ false); } - sm.quit(); - sm.cleanup(); } - mDeviceDescriptors.clear(); mGroupDescriptors.clear(); } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ false); } // Cleanup native interfaces @@ -614,7 +630,9 @@ public class LeAudioService extends ProfileService { return false; } - mGroupLock.lock(); + LeAudioStateMachine sm = null; + + groupMutexLock(/* isReadOnly */ false); try { boolean isInbandRingtoneEnabled = false; int groupId = getGroupId(device); @@ -626,16 +644,59 @@ public class LeAudioService extends ProfileService { return false; } - LeAudioStateMachine sm = getOrCreateStateMachine(device); + sm = getOrCreateStateMachine(device); if (sm == null) { Log.e(TAG, "Ignored connect request for " + device + " : no state machine"); return false; } + + if (!mFeatureFlags.leaudioApiSynchronizedBlockFix()) { + sm.sendMessage(LeAudioStateMachine.CONNECT); + } + + } finally { + groupMutexUnlock(/* isReadOnly */ false); + } + + if (mFeatureFlags.leaudioApiSynchronizedBlockFix()) { sm.sendMessage(LeAudioStateMachine.CONNECT); + } + + return true; + } + + /** + * Disconnects LE Audio for the remote bluetooth device + * + * @param device is the device with which we would like to disconnect LE Audio + * @return true if profile disconnected, false if device not connected over LE Audio + */ + boolean disconnectV2(BluetoothDevice device) { + if (DBG) { + Log.d(TAG, "disconnectV2(): " + device); + } + + LeAudioStateMachine sm = null; + + groupMutexLock(/* isReadOnly */ true); + try { + LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device); + if (descriptor == null) { + Log.e(TAG, "disconnect: No valid descriptor for device: " + device); + return false; + } + sm = descriptor.mStateMachine; } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); + } + + if (sm == null) { + Log.e(TAG, "Ignored disconnect request for " + device + " : no state machine"); + return false; } + sm.sendMessage(LeAudioStateMachine.DISCONNECT); + return true; } @@ -646,11 +707,15 @@ public class LeAudioService extends ProfileService { * @return true if profile disconnected, false if device not connected over LE Audio */ public boolean disconnect(BluetoothDevice device) { + if (mFeatureFlags.leaudioApiSynchronizedBlockFix()) { + return disconnectV2(device); + } + if (DBG) { Log.d(TAG, "disconnect(): " + device); } - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device); if (descriptor == null) { @@ -667,14 +732,14 @@ public class LeAudioService extends ProfileService { sm.sendMessage(LeAudioStateMachine.DISCONNECT); } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } return true; } public List<BluetoothDevice> getConnectedDevices() { - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { List<BluetoothDevice> devices = new ArrayList<>(); for (LeAudioDeviceDescriptor descriptor : mDeviceDescriptors.values()) { @@ -685,7 +750,7 @@ public class LeAudioService extends ProfileService { } return devices; } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } } @@ -706,7 +771,7 @@ public class LeAudioService extends ProfileService { if (bondedDevices == null) { return devices; } - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { for (BluetoothDevice device : bondedDevices) { final ParcelUuid[] featureUuids = device.getUuids(); @@ -734,7 +799,7 @@ public class LeAudioService extends ProfileService { } return devices; } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } } @@ -746,7 +811,7 @@ public class LeAudioService extends ProfileService { @VisibleForTesting List<BluetoothDevice> getDevices() { List<BluetoothDevice> devices = new ArrayList<>(); - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { for (LeAudioDeviceDescriptor descriptor : mDeviceDescriptors.values()) { if (descriptor.mStateMachine != null) { @@ -755,7 +820,7 @@ public class LeAudioService extends ProfileService { } return devices; } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } } @@ -769,7 +834,7 @@ public class LeAudioService extends ProfileService { * {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected */ public int getConnectionState(BluetoothDevice device) { - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device); if (descriptor == null) { @@ -782,7 +847,7 @@ public class LeAudioService extends ProfileService { } return sm.getConnectionState(); } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } } @@ -820,11 +885,11 @@ public class LeAudioService extends ProfileService { * @return true given group exists, otherwise false */ public boolean isValidDeviceGroup(int groupId) { - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { return groupId != LE_AUDIO_GROUP_ID_INVALID && mGroupDescriptors.containsKey(groupId); } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } } @@ -840,7 +905,7 @@ public class LeAudioService extends ProfileService { return result; } - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { for (Map.Entry<BluetoothDevice, LeAudioDeviceDescriptor> entry : mDeviceDescriptors.entrySet()) { @@ -849,7 +914,7 @@ public class LeAudioService extends ProfileService { } } } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } return result; } @@ -867,7 +932,7 @@ public class LeAudioService extends ProfileService { return result; } - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { for (Map.Entry<BluetoothDevice, LeAudioDeviceDescriptor> entry : mDeviceDescriptors.entrySet()) { @@ -876,7 +941,7 @@ public class LeAudioService extends ProfileService { } } } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } return result; } @@ -885,7 +950,7 @@ public class LeAudioService extends ProfileService { * Get the active device group id */ public Integer getActiveGroupId() { - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { for (Map.Entry<Integer, LeAudioGroupDescriptor> entry : mGroupDescriptors.entrySet()) { LeAudioGroupDescriptor descriptor = entry.getValue(); @@ -894,7 +959,7 @@ public class LeAudioService extends ProfileService { } } } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } return LE_AUDIO_GROUP_ID_INVALID; } @@ -1214,7 +1279,7 @@ public class LeAudioService extends ProfileService { } private boolean areAllGroupsInNotActiveState() { - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { for (Map.Entry<Integer, LeAudioGroupDescriptor> entry : mGroupDescriptors.entrySet()) { LeAudioGroupDescriptor descriptor = entry.getValue(); @@ -1223,7 +1288,7 @@ public class LeAudioService extends ProfileService { } } } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } return true; } @@ -1232,7 +1297,7 @@ public class LeAudioService extends ProfileService { if (groupId == LE_AUDIO_GROUP_ID_INVALID) { return null; } - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { LeAudioGroupDescriptor groupDescriptor = getGroupDescriptor(groupId); if (groupDescriptor == null) { @@ -1259,7 +1324,7 @@ public class LeAudioService extends ProfileService { return groupDescriptor.mCurrentLeadDevice; } } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } return null; } @@ -1490,7 +1555,7 @@ public class LeAudioService extends ProfileService { } boolean allLeAudioDevicesConnected() { - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { for (Map.Entry<BluetoothDevice, LeAudioDeviceDescriptor> deviceEntry : mDeviceDescriptors.entrySet()) { @@ -1507,7 +1572,7 @@ public class LeAudioService extends ProfileService { } } } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } return true; } @@ -1767,7 +1832,7 @@ public class LeAudioService extends ProfileService { } private void clearInactiveDueToContextTypeFlags() { - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { for (Map.Entry<Integer, LeAudioGroupDescriptor> groupEntry : mGroupDescriptors.entrySet()) { @@ -1780,7 +1845,7 @@ public class LeAudioService extends ProfileService { } } } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } } @@ -1997,7 +2062,7 @@ public class LeAudioService extends ProfileService { Log.d(TAG, "connect(): " + storedDevice); } - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { LeAudioStateMachine sm = getOrCreateStateMachine(storedDevice); if (sm == null) { @@ -2007,7 +2072,7 @@ public class LeAudioService extends ProfileService { } sm.sendMessage(LeAudioStateMachine.CONNECT); } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } } } @@ -2027,7 +2092,7 @@ public class LeAudioService extends ProfileService { } private void clearLostDevicesWhileStreaming(LeAudioGroupDescriptor descriptor) { - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { if (DBG) { Log.d(TAG, "Clearing lost dev: " + descriptor.mLostLeadDeviceWhileStreaming); @@ -2052,7 +2117,7 @@ public class LeAudioService extends ProfileService { } descriptor.mLostLeadDeviceWhileStreaming = null; } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } } @@ -2129,7 +2194,7 @@ public class LeAudioService extends ProfileService { } private void handleGroupTransitToActive(int groupId) { - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId); if (descriptor == null || descriptor.mIsActive) { @@ -2146,12 +2211,12 @@ public class LeAudioService extends ProfileService { updateInbandRingtoneForTheGroup(groupId); } } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } } private void handleGroupTransitToInactive(int groupId) { - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId); if (descriptor == null || !descriptor.mIsActive) { @@ -2187,7 +2252,7 @@ public class LeAudioService extends ProfileService { notifyGroupStatusChanged(groupId, LeAudioStackEvent.GROUP_STATUS_INACTIVE); updateInbandRingtoneForTheGroup(groupId); } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } } @@ -2296,7 +2361,7 @@ public class LeAudioService extends ProfileService { return; } - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { LeAudioGroupDescriptor groupDescriptor = getGroupDescriptor(groupId); if (groupDescriptor == null) { @@ -2356,7 +2421,7 @@ public class LeAudioService extends ProfileService { } } } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } } @@ -2479,7 +2544,7 @@ public class LeAudioService extends ProfileService { if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { // Some events require device state machine - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { LeAudioDeviceDescriptor deviceDescriptor = getDeviceDescriptor(device); if (deviceDescriptor == null) { @@ -2558,7 +2623,7 @@ public class LeAudioService extends ProfileService { sm.sendMessage(LeAudioStateMachine.STACK_EVENT, stackEvent); return; } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_GROUP_NODE_STATUS_CHANGED) { int groupId = stackEvent.valueInt1; @@ -2628,7 +2693,7 @@ public class LeAudioService extends ProfileService { int src_audio_location = stackEvent.valueInt4; int available_contexts = stackEvent.valueInt5; - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId); if (descriptor != null) { @@ -2657,7 +2722,7 @@ public class LeAudioService extends ProfileService { Log.e(TAG, "messageFromNative: no descriptors for group: " + groupId); } } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_SINK_AUDIO_LOCATION_AVAILABLE) { Objects.requireNonNull(stackEvent.device, @@ -2955,65 +3020,85 @@ public class LeAudioService extends ProfileService { return; } - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { - LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device); - if (descriptor == null) { - Log.e(TAG, "bondStateChanged: No valid descriptor for device: " + device); - return; - } + try { + LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device); + if (descriptor == null) { + Log.e(TAG, "bondStateChanged: No valid descriptor for device: " + device); + return; + } - if (descriptor.mGroupId != LE_AUDIO_GROUP_ID_INVALID) { - /* In case device is still in the group, let's remove it */ - mLeAudioNativeInterface.groupRemoveNode(descriptor.mGroupId, device); - } + if (descriptor.mGroupId != LE_AUDIO_GROUP_ID_INVALID) { + /* In case device is still in the group, let's remove it */ + mLeAudioNativeInterface.groupRemoveNode(descriptor.mGroupId, device); + } - descriptor.mGroupId = LE_AUDIO_GROUP_ID_INVALID; - descriptor.mSinkAudioLocation = BluetoothLeAudio.AUDIO_LOCATION_INVALID; - descriptor.mDirection = AUDIO_DIRECTION_NONE; + descriptor.mGroupId = LE_AUDIO_GROUP_ID_INVALID; + descriptor.mSinkAudioLocation = BluetoothLeAudio.AUDIO_LOCATION_INVALID; + descriptor.mDirection = AUDIO_DIRECTION_NONE; - LeAudioStateMachine sm = descriptor.mStateMachine; - if (sm == null) { - return; - } - if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { - Log.w(TAG, "Device is not disconnected yet."); - disconnect(device); - return; + LeAudioStateMachine sm = descriptor.mStateMachine; + if (sm == null) { + return; + } + if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { + Log.w(TAG, "Device is not disconnected yet."); + disconnect(device); + return; + } + } finally { + // Reduce size of critical section when this feature is enabled + if (mFeatureFlags.leaudioApiSynchronizedBlockFix()) { + groupMutexUnlock(/* isReadOnly */ true); + } } removeStateMachine(device); removeAuthorizationInfoForRelatedProfiles(device); } finally { - mGroupLock.unlock(); + if (!mFeatureFlags.leaudioApiSynchronizedBlockFix()) { + groupMutexUnlock(/* isReadOnly */ true); + } } } private void removeStateMachine(BluetoothDevice device) { - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { - LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device); - if (descriptor == null) { - Log.e(TAG, "removeStateMachine: No valid descriptor for device: " + device); - return; - } + try { + LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device); + if (descriptor == null) { + Log.e(TAG, "removeStateMachine: No valid descriptor for device: " + device); + return; + } - LeAudioStateMachine sm = descriptor.mStateMachine; - if (sm == null) { - Log.w(TAG, "removeStateMachine: device " + device - + " does not have a state machine"); - return; + LeAudioStateMachine sm = descriptor.mStateMachine; + if (sm == null) { + Log.w( + TAG, + "removeStateMachine: device " + + device + + " does not have a state machine"); + return; + } + Log.i(TAG, "removeStateMachine: removing state machine for device: " + device); + sm.quit(); + sm.cleanup(); + descriptor.mStateMachine = null; + } finally { + if (mFeatureFlags.leaudioApiSynchronizedBlockFix()) { + // Upgrade to write lock + groupMutexUnlock(/* isReadOnly */ true); + groupMutexLock(/* isReadOnly */ false); + } } - Log.i(TAG, "removeStateMachine: removing state machine for device: " + device); - sm.quit(); - sm.cleanup(); - descriptor.mStateMachine = null; - mDeviceDescriptors.remove(device); if (!isScannerNeeded()) { stopAudioServersBackgroundScan(); } } finally { - mGroupLock.unlock(); + /* Note, when flag is disabled, isReadyOnly param have no impact */ + groupMutexUnlock(/* isReadOnly */ false); } } @@ -3059,11 +3144,104 @@ public class LeAudioService extends ProfileService { } } + /** Process a change for disconnection of a device. */ + synchronized void deviceDisconnectedV2(BluetoothDevice device, boolean hasFallbackDevice) { + if (DBG) { + Log.d(TAG, "deviceDisconnectedV2 " + device); + } + + int groupId = LE_AUDIO_GROUP_ID_INVALID; + groupMutexLock(/* isReadOnly */ true); + try { + LeAudioDeviceDescriptor deviceDescriptor = getDeviceDescriptor(device); + if (deviceDescriptor == null) { + Log.e(TAG, "deviceDisconnected: No valid descriptor for device: " + device); + return; + } + groupId = deviceDescriptor.mGroupId; + } finally { + groupMutexUnlock(/* isReadOnly */ true); + } + + int bondState = mAdapterService.getBondState(device); + if (bondState == BluetoothDevice.BOND_NONE) { + if (DBG) { + Log.d(TAG, device + " is unbond. Remove state machine"); + } + + removeStateMachine(device); + removeAuthorizationInfoForRelatedProfiles(device); + } + + if (!isScannerNeeded()) { + stopAudioServersBackgroundScan(); + } + + groupMutexLock(/* isReadOnly */ true); + try { + LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId); + if (descriptor == null) { + Log.e(TAG, "deviceDisconnected: no descriptors for group: " + groupId); + return; + } + + List<BluetoothDevice> connectedDevices = getConnectedPeerDevices(groupId); + /* 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(groupId).isEmpty()) { + descriptor.mIsConnected = false; + if (descriptor.mIsActive) { + /* Notify Native layer */ + removeActiveDevice(hasFallbackDevice); + descriptor.mIsActive = false; + /* Update audio framework */ + updateActiveDevices( + groupId, + descriptor.mDirection, + descriptor.mDirection, + descriptor.mIsActive, + hasFallbackDevice, + false); + return; + } + } + + if (descriptor.mIsActive + || Objects.equals(mActiveAudioOutDevice, device) + || Objects.equals(mActiveAudioInDevice, device)) { + updateActiveDevices( + groupId, + descriptor.mDirection, + descriptor.mDirection, + descriptor.mIsActive, + hasFallbackDevice, + false); + } + } finally { + groupMutexUnlock(/* isReadOnly */ true); + } + } + /** * Process a change for disconnection of a device. */ public synchronized void deviceDisconnected(BluetoothDevice device, boolean hasFallbackDevice) { - mGroupLock.lock(); + if (mFeatureFlags.leaudioApiSynchronizedBlockFix()) { + deviceDisconnectedV2(device, hasFallbackDevice); + return; + } + + if (DBG) { + Log.d(TAG, "deviceDisconnected " + device); + } + + groupMutexLock(/* isReadOnly */ true); try { LeAudioDeviceDescriptor deviceDescriptor = getDeviceDescriptor(device); if (deviceDescriptor == null) { @@ -3127,7 +3305,7 @@ public class LeAudioService extends ProfileService { false); } } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } } @@ -3377,7 +3555,7 @@ public class LeAudioService extends ProfileService { return LE_AUDIO_GROUP_ID_INVALID; } - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device); if (descriptor == null) { @@ -3387,7 +3565,7 @@ public class LeAudioService extends ProfileService { return descriptor.mGroupId; } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } } @@ -3493,49 +3671,51 @@ public class LeAudioService extends ProfileService { mBluetoothEnabled = true; - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { - if (mDeviceDescriptors.isEmpty()) { - return; + try { + if (mDeviceDescriptors.isEmpty()) { + return; + } + } finally { + if (!mFeatureFlags.leaudioApiSynchronizedBlockFix()) { + // Keep previous behavior where a lock is released and acquired immediately + groupMutexUnlock(/* isReadOnly */ true); + groupMutexLock(/* isReadOnly */ true); + } } - } finally { - mGroupLock.unlock(); - } - - mGroupLock.lock(); - try { for (BluetoothDevice device : mDeviceDescriptors.keySet()) { if (getConnectionPolicy(device) != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { setAuthorizationForRelatedProfiles(device, true); } } } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } startAudioServersBackgroundScan(/* retry = */ false); } private LeAudioGroupDescriptor getGroupDescriptor(int groupId) { - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { return mGroupDescriptors.get(groupId); } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } } private LeAudioDeviceDescriptor getDeviceDescriptor(BluetoothDevice device) { - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { return mDeviceDescriptors.get(device); } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } } private void handleGroupNodeAdded(BluetoothDevice device, int groupId) { - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ false); try { if (DBG) { Log.d(TAG, "Device " + device + " added to group " + groupId); @@ -3571,7 +3751,7 @@ public class LeAudioService extends ProfileService { notifyGroupNodeAdded(device, groupId); } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ false); } if (mBluetoothEnabled) { @@ -3604,7 +3784,8 @@ public class LeAudioService extends ProfileService { Log.d(TAG, "Removing device " + device + " grom group " + groupId); } - mGroupLock.lock(); + boolean isGroupEmpty = true; + groupMutexLock(/* isReadOnly */ true); try { LeAudioGroupDescriptor groupDescriptor = getGroupDescriptor(groupId); if (groupDescriptor == null) { @@ -3625,8 +3806,6 @@ public class LeAudioService extends ProfileService { } deviceDescriptor.mGroupId = LE_AUDIO_GROUP_ID_INVALID; - boolean isGroupEmpty = true; - for (LeAudioDeviceDescriptor descriptor : mDeviceDescriptors.values()) { if (descriptor.mGroupId == groupId) { isGroupEmpty = false; @@ -3642,7 +3821,9 @@ public class LeAudioService extends ProfileService { || Objects.equals(device, mActiveAudioInDevice)) { handleGroupTransitToInactive(groupId); } - mGroupDescriptors.remove(groupId); + if (!mFeatureFlags.leaudioApiSynchronizedBlockFix()) { + mGroupDescriptors.remove(groupId); + } if (mUnicastGroupIdDeactivatedForBroadcastTransition == groupId) { updateFallbackUnicastGroupIdForBroadcast(LE_AUDIO_GROUP_ID_INVALID); @@ -3650,7 +3831,16 @@ public class LeAudioService extends ProfileService { } notifyGroupNodeRemoved(device, groupId); } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); + } + + if (isGroupEmpty && mFeatureFlags.leaudioApiSynchronizedBlockFix()) { + groupMutexLock(/* isReadOnly */ false); + try { + mGroupDescriptors.remove(groupId); + } finally { + groupMutexUnlock(/* isReadOnly */ false); + } } setAuthorizationForRelatedProfiles(device, false); @@ -4717,7 +4907,7 @@ public class LeAudioService extends ProfileService { + mLeAudioInbandRingtoneSupportedByPlatform); int numberOfUngroupedDevs = 0; - mGroupLock.lock(); + groupMutexLock(/* isReadOnly */ true); try { for (Map.Entry<Integer, LeAudioGroupDescriptor> groupEntry : mGroupDescriptors.entrySet()) { @@ -4763,7 +4953,7 @@ public class LeAudioService extends ProfileService { } } } finally { - mGroupLock.unlock(); + groupMutexUnlock(/* isReadOnly */ true); } if (numberOfUngroupedDevs > 0) { @@ -4785,4 +4975,28 @@ public class LeAudioService extends ProfileService { } } } + + private void groupMutexLock(boolean isReadOnly) { + if (mFeatureFlags.leaudioApiSynchronizedBlockFix()) { + if (isReadOnly) { + mGroupReadLock.lock(); + } else { + mGroupWriteLock.lock(); + } + } else { + mGroupLock.lock(); + } + } + + private void groupMutexUnlock(boolean isReadOnly) { + if (mFeatureFlags.leaudioApiSynchronizedBlockFix()) { + if (isReadOnly) { + mGroupReadLock.unlock(); + } else { + mGroupWriteLock.unlock(); + } + } else { + mGroupLock.unlock(); + } + } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBinderTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBinderTest.java index 4c6d90933920e02cd7879ddf1a9d9773f9893efc..ad136454979af149473cf3f465b6f79acc30408f 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBinderTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBinderTest.java @@ -84,6 +84,8 @@ public class LeAudioBinderTest { doReturn(mAudioRoutingManager).when(mAdapterService).getActiveDeviceManager(); mFakeFlagsImpl = new FakeFeatureFlagsImpl(); + mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_API_SYNCHRONIZED_BLOCK_FIX, false); + mLeAudioService = spy( new LeAudioService( diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java index 33894b8500817769dd58d39da8bb30ba68135f2f..81d19db69a8fbfb15b99892490cfd16cb242f133 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java @@ -192,6 +192,7 @@ public class LeAudioBroadcastServiceTest { mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_UNICAST_INACTIVATE_DEVICE_BASED_ON_CONTEXT, false); mFakeFlagsImpl.setFlag(Flags.FLAG_AUDIO_ROUTING_CENTRALIZATION, false); mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES, false); + mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_API_SYNCHRONIZED_BLOCK_FIX, false); mService.setFeatureFlags(mFakeFlagsImpl); mService.mAudioManager = mAudioManager; 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 46c35d35160e65b99a4779fd41dc746cd2aff173..022abb3c2c236c6774738e146737c81325a7042e 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 @@ -200,6 +200,7 @@ public class LeAudioServiceTest { mFakeFlagsImpl.setFlag(Flags.FLAG_AUDIO_ROUTING_CENTRALIZATION, false); mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES, false); mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_MCS_TBS_AUTHORIZATION_REBOND_FIX, false); + mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_API_SYNCHRONIZED_BLOCK_FIX, false); mService.setFeatureFlags(mFakeFlagsImpl); mService.mAudioManager = mAudioManager; @@ -939,6 +940,12 @@ public class LeAudioServiceTest { verify(mMcpService, times(1)).removeDeviceAuthorizationInfo(mLeftDevice); } + @Test + public void testAuthorizationInfoRemovedFromTbsMcsOnUnbondEventsWithSynchBlockFixFlag() { + mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_API_SYNCHRONIZED_BLOCK_FIX, true); + + testAuthorizationInfoRemovedFromTbsMcsOnUnbondEvents(); + } /** * Test that a CONNECTION_STATE_DISCONNECTED Le Audio stack event will remove the state * machine only if the device is unbond.