Loading android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java +51 −20 Original line number Diff line number Diff line Loading @@ -252,7 +252,7 @@ class ActiveDeviceManager { if (mHfpConnectedDevices.contains(device)) { setA2dpActiveDevice(device); setHfpActiveDevice(device); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); return; } DatabaseManager dbManager = mAdapterService.getDatabase(); Loading @@ -260,7 +260,7 @@ class ActiveDeviceManager { if (dbManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET) != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { setA2dpActiveDevice(device); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } } } Loading @@ -281,7 +281,7 @@ class ActiveDeviceManager { if (mA2dpConnectedDevices.contains(device)) { setA2dpActiveDevice(device); setHfpActiveDevice(device); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); return; } DatabaseManager dbManager = mAdapterService.getDatabase(); Loading @@ -289,7 +289,7 @@ class ActiveDeviceManager { if (dbManager.getProfileConnectionPolicy(device, BluetoothProfile.A2DP) != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { setHfpActiveDevice(device); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } } } Loading @@ -308,7 +308,7 @@ class ActiveDeviceManager { setHearingAidActiveDevice(device); setA2dpActiveDevice(null, true); setHfpActiveDevice(null); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } } Loading @@ -317,9 +317,17 @@ class ActiveDeviceManager { if (DBG) { Log.d(TAG, "handleLeAudioConnected: " + device); } final LeAudioService leAudioService = mFactory.getLeAudioService(); if (leAudioService == null || device == null) { return; } leAudioService.deviceConnected(device); if (mLeAudioConnectedDevices.contains(device)) { return; // The device is already connected } mLeAudioConnectedDevices.add(device); if (mHearingAidActiveDevices.isEmpty() && mLeHearingAidActiveDevice == null Loading Loading @@ -409,14 +417,23 @@ class ActiveDeviceManager { if (DBG) { Log.d(TAG, "handleLeAudioDisconnected: " + device); } final LeAudioService leAudioService = mFactory.getLeAudioService(); if (leAudioService == null || device == null) { return; } mLeAudioConnectedDevices.remove(device); mLeHearingAidConnectedDevices.remove(device); boolean hasFallbackDevice = false; if (Objects.equals(mLeAudioActiveDevice, device)) { if (mLeAudioConnectedDevices.isEmpty()) { setLeAudioActiveDevice(null); hasFallbackDevice = setFallbackDeviceActiveLocked(); if (!hasFallbackDevice) { setLeAudioActiveDevice(null, false); } setFallbackDeviceActiveLocked(); } leAudioService.deviceDisconnected(device, hasFallbackDevice); } } Loading @@ -440,7 +457,7 @@ class ActiveDeviceManager { } if (device != null && !Objects.equals(mA2dpActiveDevice, device)) { setHearingAidActiveDevice(null); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } if (mHfpConnectedDevices.contains(device)) { setHfpActiveDevice(device); Loading @@ -457,7 +474,7 @@ class ActiveDeviceManager { } if (device != null && !Objects.equals(mHfpActiveDevice, device)) { setHearingAidActiveDevice(null); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } if (mA2dpConnectedDevices.contains(device)) { setA2dpActiveDevice(device); Loading Loading @@ -487,7 +504,7 @@ class ActiveDeviceManager { if (device != null) { setA2dpActiveDevice(null, true); setHfpActiveDevice(null); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } } } Loading Loading @@ -698,18 +715,32 @@ class ActiveDeviceManager { } } private void setLeAudioActiveDevice(BluetoothDevice device) { synchronized (mLock) { private void setLeAudioActiveDevice(@NonNull BluetoothDevice device) { setLeAudioActiveDevice(device, false); } private void setLeAudioActiveDevice(@Nullable BluetoothDevice device, boolean hasFallbackDevice) { if (DBG) { Log.d(TAG, "setLeAudioActiveDevice(" + device + ")"); Log.d(TAG, "setLeAudioActiveDevice(" + device + ")" + (device == null ? " hasFallbackDevice=" + hasFallbackDevice : "")); } synchronized (mLock) { final LeAudioService leAudioService = mFactory.getLeAudioService(); if (leAudioService == null) { return; } if (!leAudioService.setActiveDevice(device)) { boolean success; if (device == null) { success = leAudioService.removeActiveDevice(hasFallbackDevice); } else { success = leAudioService.setActiveDevice(device); } if (!success) { return; } mLeAudioActiveDevice = device; if (device == null) { mLeHearingAidActiveDevice = null; Loading Loading @@ -764,7 +795,7 @@ class ActiveDeviceManager { setHearingAidActiveDevice(device); setA2dpActiveDevice(null, true); setHfpActiveDevice(null); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } else { if (DBG) { Log.d(TAG, "set LE hearing aid device active: " + device); Loading Loading @@ -818,7 +849,7 @@ class ActiveDeviceManager { setA2dpActiveDevice(device); if (headsetFallbackDevice != null) { setHfpActiveDevice(device); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } } else { if (DBG) { Loading @@ -836,7 +867,7 @@ class ActiveDeviceManager { setHfpActiveDevice(device); if (a2dpFallbackDevice != null) { setA2dpActiveDevice(a2dpFallbackDevice); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } } else { if (DBG) { Loading Loading @@ -920,6 +951,6 @@ class ActiveDeviceManager { setA2dpActiveDevice(null, true); setHfpActiveDevice(null); setHearingAidActiveDevice(null); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } } android/app/src/com/android/bluetooth/btservice/AdapterService.java +5 −1 Original line number Diff line number Diff line Loading @@ -5311,8 +5311,12 @@ public class AdapterService extends Service { || mLeAudioService.getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { Log.i(TAG, "setActiveDevice: Setting active Le Audio device " + device); if (device == null) { mLeAudioService.removeActiveDevice(false); } else { mLeAudioService.setActiveDevice(device); } } if (setA2dp && mA2dpService != null && (device == null || mA2dpService.getConnectionPolicy(device) Loading android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +102 −98 Original line number Diff line number Diff line Loading @@ -193,7 +193,6 @@ public class LeAudioService extends ProfileService { new LinkedHashMap<>(); private BroadcastReceiver mBondStateChangedReceiver; private BroadcastReceiver mConnectionStateChangedReceiver; private BroadcastReceiver mMuteStateChangedReceiver; private int mStoredRingerMode = -1; private Handler mHandler = new Handler(Looper.getMainLooper()); Loading Loading @@ -276,10 +275,6 @@ public class LeAudioService extends ProfileService { 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); Loading Loading @@ -346,7 +341,7 @@ public class LeAudioService extends ProfileService { } mHandler.removeCallbacks(this::init); setActiveDevice(null); removeActiveDevice(false); if (mTmapGattServer == null) { Log.w(TAG, "TMAP GATT server should never be null before stop() is called"); Loading @@ -365,7 +360,7 @@ public class LeAudioService extends ProfileService { if (descriptor.mIsActive) { descriptor.mIsActive = false; updateActiveDevices(group_id, descriptor.mDirection, AUDIO_DIRECTION_NONE, descriptor.mIsActive); descriptor.mIsActive, false); break; } } Loading Loading @@ -401,8 +396,6 @@ public class LeAudioService extends ProfileService { // Unregister broadcast receivers unregisterReceiver(mBondStateChangedReceiver); mBondStateChangedReceiver = null; unregisterReceiver(mConnectionStateChangedReceiver); mConnectionStateChangedReceiver = null; unregisterReceiver(mMuteStateChangedReceiver); mMuteStateChangedReceiver = null; Loading Loading @@ -1182,10 +1175,12 @@ public class LeAudioService extends ProfileService { * @param newSupportedAudioDirections new supported audio directions for group of devices * @param oldSupportedAudioDirections old supported audio directions for group of devices * @param isActive if there is new active group * @param hasFallbackDevice whether any fallback device exists when deactivating * the current active device. * @return true if group is active after change false otherwise. */ private boolean updateActiveDevices(Integer groupId, Integer oldSupportedAudioDirections, Integer newSupportedAudioDirections, boolean isActive) { Integer newSupportedAudioDirections, boolean isActive, boolean hasFallbackDevice) { BluetoothDevice device = null; BluetoothDevice previousActiveOutDevice = mActiveAudioOutDevice; BluetoothDevice previousActiveInDevice = mActiveAudioInDevice; Loading @@ -1212,7 +1207,7 @@ public class LeAudioService extends ProfileService { volume = getAudioDeviceGroupVolume(groupId); } final boolean suppressNoisyIntent = (mActiveAudioOutDevice != null) final boolean suppressNoisyIntent = hasFallbackDevice || mActiveAudioOutDevice != null || (getConnectionState(previousActiveOutDevice) == BluetoothProfile.STATE_CONNECTED); Loading @@ -1239,8 +1234,11 @@ public class LeAudioService extends ProfileService { /** * Set the active device group. * * @param hasFallbackDevice hasFallbackDevice whether any fallback device exists when * {@code device} is null. */ private void setActiveGroupWithDevice(BluetoothDevice device) { private void setActiveGroupWithDevice(BluetoothDevice device, boolean hasFallbackDevice) { int groupId = LE_AUDIO_GROUP_ID_INVALID; if (device != null) { Loading Loading @@ -1277,28 +1275,40 @@ public class LeAudioService extends ProfileService { * 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); handleGroupTransitToInactive(currentlyActiveGroupId, hasFallbackDevice); } } /** * Remove the current active group. * * @param hasFallbackDevice whether any fallback device exists when deactivating * the current active device. * @return true on success, otherwise false */ public boolean removeActiveDevice(boolean hasFallbackDevice) { /* Clear active group */ setActiveGroupWithDevice(null, hasFallbackDevice); return true; } /** * Set the active group represented by device. * * @param device the new active device * @param device the new active device. Should not be null. * @return true on success, otherwise false */ public boolean setActiveDevice(BluetoothDevice device) { /* Clear active group */ if (device == null) { setActiveGroupWithDevice(device); return true; Log.e(TAG, "device should not be null!"); return removeActiveDevice(false); } if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { Log.e(TAG, "setActiveDevice(" + device + "): failed because group device is not " + "connected"); return false; } setActiveGroupWithDevice(device); setActiveGroupWithDevice(device, false); return true; } Loading Loading @@ -1455,7 +1465,7 @@ public class LeAudioService extends ProfileService { } descriptor.mIsActive = updateActiveDevices(groupId, AUDIO_DIRECTION_NONE, descriptor.mDirection, true); descriptor.mDirection, true, false); if (descriptor.mIsActive) { notifyGroupStatusChanged(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE); Loading @@ -1464,7 +1474,7 @@ public class LeAudioService extends ProfileService { } } private void handleGroupTransitToInactive(int groupId) { private void handleGroupTransitToInactive(int groupId, boolean hasFallbackDevice) { synchronized (mGroupLock) { LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId); if (descriptor == null || !descriptor.mIsActive) { Loading @@ -1475,7 +1485,7 @@ public class LeAudioService extends ProfileService { descriptor.mIsActive = false; updateActiveDevices(groupId, descriptor.mDirection, AUDIO_DIRECTION_NONE, descriptor.mIsActive); descriptor.mIsActive, hasFallbackDevice); /* Clear lost devices */ if (DBG) Log.d(TAG, "Clear for group: " + groupId); clearLostDevicesWhileStreaming(descriptor); Loading Loading @@ -1725,7 +1735,7 @@ public class LeAudioService extends ProfileService { if (descriptor.mIsActive) { descriptor.mIsActive = updateActiveDevices(groupId, descriptor.mDirection, direction, descriptor.mIsActive); descriptor.mIsActive, false); if (!descriptor.mIsActive) { notifyGroupStatusChanged(groupId, BluetoothLeAudio.GROUP_STATUS_INACTIVE); Loading Loading @@ -1766,7 +1776,7 @@ public class LeAudioService extends ProfileService { break; } case LeAudioStackEvent.GROUP_STATUS_INACTIVE: { handleGroupTransitToInactive(groupId); handleGroupTransitToInactive(groupId, false); break; } case LeAudioStackEvent.GROUP_STATUS_TURNED_IDLE_DURING_CALL: { Loading Loading @@ -2007,21 +2017,16 @@ public class LeAudioService extends ProfileService { return result; } @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; } /** * Process a change for connection of a device. */ public synchronized void deviceConnected(BluetoothDevice device) { LeAudioDeviceDescriptor deviceDescriptor = getDeviceDescriptor(device); if (deviceDescriptor == null) { Log.e(TAG, "connectionStateChanged: No valid descriptor for device: " + device); Log.e(TAG, "deviceConnected: No valid descriptor for device: " + device); return; } if (toState == BluetoothProfile.STATE_CONNECTED) { if (deviceDescriptor.mGroupId == LE_AUDIO_GROUP_ID_INVALID || getConnectedPeerDevices(deviceDescriptor.mGroupId).size() == 1) { // Log LE Audio connection event if we are the first device in a set Loading @@ -2034,12 +2039,21 @@ public class LeAudioService extends ProfileService { if (descriptor != null) { descriptor.mIsConnected = true; } else { Log.e(TAG, "connectionStateChanged(STATE_CONNECTED): no descriptors for group: " Log.e(TAG, "deviceConnected: no descriptors for group: " + deviceDescriptor.mGroupId); } } // Check if the device is disconnected - if unbond, remove the state machine if (toState == BluetoothProfile.STATE_DISCONNECTED) { /** * Process a change for disconnection of a device. */ public synchronized void deviceDisconnected(BluetoothDevice device, boolean hasFallbackDevice) { LeAudioDeviceDescriptor deviceDescriptor = getDeviceDescriptor(device); if (deviceDescriptor == null) { Log.e(TAG, "deviceDisconnected: No valid descriptor for device: " + device); return; } int bondState = mAdapterService.getBondState(device); if (bondState == BluetoothDevice.BOND_NONE) { if (DBG) { Loading @@ -2051,8 +2065,8 @@ public class LeAudioService extends ProfileService { synchronized (mGroupLock) { LeAudioGroupDescriptor descriptor = getGroupDescriptor(deviceDescriptor.mGroupId); if (descriptor == null) { Log.e(TAG, "connectionStateChanged(STATE_DISCONNECTED): no descriptors for " + "group: " + deviceDescriptor.mGroupId); Log.e(TAG, "deviceDisconnected: no descriptors for group: " + deviceDescriptor.mGroupId); return; } Loading @@ -2069,13 +2083,14 @@ public class LeAudioService extends ProfileService { descriptor.mIsConnected = false; if (descriptor.mIsActive) { /* Notify Native layer */ setActiveDevice(null); removeActiveDevice(hasFallbackDevice); descriptor.mIsActive = false; /* Update audio framework */ updateActiveDevices(deviceDescriptor.mGroupId, descriptor.mDirection, descriptor.mDirection, descriptor.mIsActive); descriptor.mIsActive, hasFallbackDevice); return; } } Loading @@ -2084,23 +2099,9 @@ public class LeAudioService extends ProfileService { updateActiveDevices(deviceDescriptor.mGroupId, descriptor.mDirection, descriptor.mDirection, descriptor.mIsActive); } } } descriptor.mIsActive, hasFallbackDevice); } 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); } } Loading Loading @@ -2230,7 +2231,7 @@ public class LeAudioService extends ProfileService { } if (getActiveGroupId() != LE_AUDIO_GROUP_ID_INVALID) { mHfpHandoverDevice = hfpHandoverDevice; setActiveDevice(null); removeActiveDevice(true); } } Loading Loading @@ -2523,7 +2524,7 @@ public class LeAudioService extends ProfileService { */ if (Objects.equals(device, mActiveAudioOutDevice) || Objects.equals(device, mActiveAudioInDevice)) { handleGroupTransitToInactive(groupId); handleGroupTransitToInactive(groupId, false); } mGroupDescriptors.remove(groupId); } Loading Loading @@ -2967,15 +2968,18 @@ public class LeAudioService extends ProfileService { public void setActiveDevice(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver) { try { Objects.requireNonNull(device, "device cannot be null"); Objects.requireNonNull(source, "source cannot be null"); Objects.requireNonNull(receiver, "receiver cannot be null"); LeAudioService service = getService(source); boolean result = false; if (service != null) { if (device == null) { result = service.removeActiveDevice(true); } else { result = service.setActiveDevice(device); } } receiver.send(result); } catch (RuntimeException e) { receiver.propagateException(e); Loading android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java +10 −7 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.bluetooth.btservice; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.any; import static org.mockito.Mockito.isNull; import static org.mockito.Mockito.never; Loading Loading @@ -127,6 +128,7 @@ public class ActiveDeviceManagerTest { when(mHeadsetService.setActiveDevice(any())).thenReturn(true); when(mHearingAidService.setActiveDevice(any())).thenReturn(true); when(mLeAudioService.setActiveDevice(any())).thenReturn(true); when(mLeAudioService.removeActiveDevice(anyBoolean())).thenReturn(true); List<BluetoothDevice> connectedHearingAidDevices = new ArrayList<>(); connectedHearingAidDevices.add(mHearingAidDevice); Loading Loading @@ -443,7 +445,7 @@ public class ActiveDeviceManagerTest { verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice); leAudioDisconnected(mLeAudioDevice); verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(isNull()); verify(mLeAudioService, timeout(TIMEOUT_MS)).removeActiveDevice(false); } /** Loading @@ -457,10 +459,11 @@ public class ActiveDeviceManagerTest { leAudioConnected(mSecondaryAudioDevice); verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice); Mockito.clearInvocations(mLeAudioService); leAudioActiveDeviceChanged(mLeAudioDevice); // Don't call mLeAudioService.setActiveDevice() TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper()); verify(mLeAudioService, times(1)).setActiveDevice(mLeAudioDevice); verify(mLeAudioService, never()).setActiveDevice(any(BluetoothDevice.class)); Assert.assertEquals(mLeAudioDevice, mActiveDeviceManager.getLeAudioActiveDevice()); } Loading Loading @@ -520,10 +523,10 @@ public class ActiveDeviceManagerTest { a2dpActiveDeviceChanged(mA2dpDevice); TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper()); verify(mLeAudioService).setActiveDevice(isNull()); verify(mLeAudioService).removeActiveDevice(true); verify(mA2dpService).setActiveDevice(mA2dpDevice); Assert.assertEquals(mA2dpDevice, mActiveDeviceManager.getA2dpActiveDevice()); Assert.assertEquals(null, mActiveDeviceManager.getLeAudioActiveDevice()); Assert.assertNull(mActiveDeviceManager.getLeAudioActiveDevice()); } /** Loading @@ -536,10 +539,10 @@ public class ActiveDeviceManagerTest { headsetActiveDeviceChanged(mHeadsetDevice); TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper()); verify(mLeAudioService).setActiveDevice(isNull()); verify(mLeAudioService).removeActiveDevice(true); verify(mHeadsetService).setActiveDevice(mHeadsetDevice); Assert.assertEquals(mHeadsetDevice, mActiveDeviceManager.getHfpActiveDevice()); Assert.assertEquals(null, mActiveDeviceManager.getLeAudioActiveDevice()); Assert.assertNull(mActiveDeviceManager.getLeAudioActiveDevice()); } /** Loading Loading @@ -578,7 +581,7 @@ public class ActiveDeviceManagerTest { Mockito.clearInvocations(mA2dpService); leAudioDisconnected(mLeAudioDevice); verify(mLeAudioService, timeout(TIMEOUT_MS).atLeast(1)).setActiveDevice(isNull()); verify(mLeAudioService, timeout(TIMEOUT_MS).atLeast(1)).removeActiveDevice(true); verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice); } Loading android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBinderTest.java +9 −0 Original line number Diff line number Diff line Loading @@ -148,6 +148,15 @@ public class LeAudioBinderTest { verify(mMockService).setActiveDevice(device); } @Test public void setActiveDevice_withNullDevice_callsRemoveActiveDevice() { AttributionSource source = new AttributionSource.Builder(0).build(); final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); mBinder.setActiveDevice(null, source, recv); verify(mMockService).removeActiveDevice(true); } @Test public void getActiveDevices() { AttributionSource source = new AttributionSource.Builder(0).build(); Loading Loading
android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java +51 −20 Original line number Diff line number Diff line Loading @@ -252,7 +252,7 @@ class ActiveDeviceManager { if (mHfpConnectedDevices.contains(device)) { setA2dpActiveDevice(device); setHfpActiveDevice(device); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); return; } DatabaseManager dbManager = mAdapterService.getDatabase(); Loading @@ -260,7 +260,7 @@ class ActiveDeviceManager { if (dbManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET) != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { setA2dpActiveDevice(device); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } } } Loading @@ -281,7 +281,7 @@ class ActiveDeviceManager { if (mA2dpConnectedDevices.contains(device)) { setA2dpActiveDevice(device); setHfpActiveDevice(device); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); return; } DatabaseManager dbManager = mAdapterService.getDatabase(); Loading @@ -289,7 +289,7 @@ class ActiveDeviceManager { if (dbManager.getProfileConnectionPolicy(device, BluetoothProfile.A2DP) != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { setHfpActiveDevice(device); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } } } Loading @@ -308,7 +308,7 @@ class ActiveDeviceManager { setHearingAidActiveDevice(device); setA2dpActiveDevice(null, true); setHfpActiveDevice(null); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } } Loading @@ -317,9 +317,17 @@ class ActiveDeviceManager { if (DBG) { Log.d(TAG, "handleLeAudioConnected: " + device); } final LeAudioService leAudioService = mFactory.getLeAudioService(); if (leAudioService == null || device == null) { return; } leAudioService.deviceConnected(device); if (mLeAudioConnectedDevices.contains(device)) { return; // The device is already connected } mLeAudioConnectedDevices.add(device); if (mHearingAidActiveDevices.isEmpty() && mLeHearingAidActiveDevice == null Loading Loading @@ -409,14 +417,23 @@ class ActiveDeviceManager { if (DBG) { Log.d(TAG, "handleLeAudioDisconnected: " + device); } final LeAudioService leAudioService = mFactory.getLeAudioService(); if (leAudioService == null || device == null) { return; } mLeAudioConnectedDevices.remove(device); mLeHearingAidConnectedDevices.remove(device); boolean hasFallbackDevice = false; if (Objects.equals(mLeAudioActiveDevice, device)) { if (mLeAudioConnectedDevices.isEmpty()) { setLeAudioActiveDevice(null); hasFallbackDevice = setFallbackDeviceActiveLocked(); if (!hasFallbackDevice) { setLeAudioActiveDevice(null, false); } setFallbackDeviceActiveLocked(); } leAudioService.deviceDisconnected(device, hasFallbackDevice); } } Loading @@ -440,7 +457,7 @@ class ActiveDeviceManager { } if (device != null && !Objects.equals(mA2dpActiveDevice, device)) { setHearingAidActiveDevice(null); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } if (mHfpConnectedDevices.contains(device)) { setHfpActiveDevice(device); Loading @@ -457,7 +474,7 @@ class ActiveDeviceManager { } if (device != null && !Objects.equals(mHfpActiveDevice, device)) { setHearingAidActiveDevice(null); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } if (mA2dpConnectedDevices.contains(device)) { setA2dpActiveDevice(device); Loading Loading @@ -487,7 +504,7 @@ class ActiveDeviceManager { if (device != null) { setA2dpActiveDevice(null, true); setHfpActiveDevice(null); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } } } Loading Loading @@ -698,18 +715,32 @@ class ActiveDeviceManager { } } private void setLeAudioActiveDevice(BluetoothDevice device) { synchronized (mLock) { private void setLeAudioActiveDevice(@NonNull BluetoothDevice device) { setLeAudioActiveDevice(device, false); } private void setLeAudioActiveDevice(@Nullable BluetoothDevice device, boolean hasFallbackDevice) { if (DBG) { Log.d(TAG, "setLeAudioActiveDevice(" + device + ")"); Log.d(TAG, "setLeAudioActiveDevice(" + device + ")" + (device == null ? " hasFallbackDevice=" + hasFallbackDevice : "")); } synchronized (mLock) { final LeAudioService leAudioService = mFactory.getLeAudioService(); if (leAudioService == null) { return; } if (!leAudioService.setActiveDevice(device)) { boolean success; if (device == null) { success = leAudioService.removeActiveDevice(hasFallbackDevice); } else { success = leAudioService.setActiveDevice(device); } if (!success) { return; } mLeAudioActiveDevice = device; if (device == null) { mLeHearingAidActiveDevice = null; Loading Loading @@ -764,7 +795,7 @@ class ActiveDeviceManager { setHearingAidActiveDevice(device); setA2dpActiveDevice(null, true); setHfpActiveDevice(null); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } else { if (DBG) { Log.d(TAG, "set LE hearing aid device active: " + device); Loading Loading @@ -818,7 +849,7 @@ class ActiveDeviceManager { setA2dpActiveDevice(device); if (headsetFallbackDevice != null) { setHfpActiveDevice(device); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } } else { if (DBG) { Loading @@ -836,7 +867,7 @@ class ActiveDeviceManager { setHfpActiveDevice(device); if (a2dpFallbackDevice != null) { setA2dpActiveDevice(a2dpFallbackDevice); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } } else { if (DBG) { Loading Loading @@ -920,6 +951,6 @@ class ActiveDeviceManager { setA2dpActiveDevice(null, true); setHfpActiveDevice(null); setHearingAidActiveDevice(null); setLeAudioActiveDevice(null); setLeAudioActiveDevice(null, true); } }
android/app/src/com/android/bluetooth/btservice/AdapterService.java +5 −1 Original line number Diff line number Diff line Loading @@ -5311,8 +5311,12 @@ public class AdapterService extends Service { || mLeAudioService.getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { Log.i(TAG, "setActiveDevice: Setting active Le Audio device " + device); if (device == null) { mLeAudioService.removeActiveDevice(false); } else { mLeAudioService.setActiveDevice(device); } } if (setA2dp && mA2dpService != null && (device == null || mA2dpService.getConnectionPolicy(device) Loading
android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +102 −98 Original line number Diff line number Diff line Loading @@ -193,7 +193,6 @@ public class LeAudioService extends ProfileService { new LinkedHashMap<>(); private BroadcastReceiver mBondStateChangedReceiver; private BroadcastReceiver mConnectionStateChangedReceiver; private BroadcastReceiver mMuteStateChangedReceiver; private int mStoredRingerMode = -1; private Handler mHandler = new Handler(Looper.getMainLooper()); Loading Loading @@ -276,10 +275,6 @@ public class LeAudioService extends ProfileService { 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); Loading Loading @@ -346,7 +341,7 @@ public class LeAudioService extends ProfileService { } mHandler.removeCallbacks(this::init); setActiveDevice(null); removeActiveDevice(false); if (mTmapGattServer == null) { Log.w(TAG, "TMAP GATT server should never be null before stop() is called"); Loading @@ -365,7 +360,7 @@ public class LeAudioService extends ProfileService { if (descriptor.mIsActive) { descriptor.mIsActive = false; updateActiveDevices(group_id, descriptor.mDirection, AUDIO_DIRECTION_NONE, descriptor.mIsActive); descriptor.mIsActive, false); break; } } Loading Loading @@ -401,8 +396,6 @@ public class LeAudioService extends ProfileService { // Unregister broadcast receivers unregisterReceiver(mBondStateChangedReceiver); mBondStateChangedReceiver = null; unregisterReceiver(mConnectionStateChangedReceiver); mConnectionStateChangedReceiver = null; unregisterReceiver(mMuteStateChangedReceiver); mMuteStateChangedReceiver = null; Loading Loading @@ -1182,10 +1175,12 @@ public class LeAudioService extends ProfileService { * @param newSupportedAudioDirections new supported audio directions for group of devices * @param oldSupportedAudioDirections old supported audio directions for group of devices * @param isActive if there is new active group * @param hasFallbackDevice whether any fallback device exists when deactivating * the current active device. * @return true if group is active after change false otherwise. */ private boolean updateActiveDevices(Integer groupId, Integer oldSupportedAudioDirections, Integer newSupportedAudioDirections, boolean isActive) { Integer newSupportedAudioDirections, boolean isActive, boolean hasFallbackDevice) { BluetoothDevice device = null; BluetoothDevice previousActiveOutDevice = mActiveAudioOutDevice; BluetoothDevice previousActiveInDevice = mActiveAudioInDevice; Loading @@ -1212,7 +1207,7 @@ public class LeAudioService extends ProfileService { volume = getAudioDeviceGroupVolume(groupId); } final boolean suppressNoisyIntent = (mActiveAudioOutDevice != null) final boolean suppressNoisyIntent = hasFallbackDevice || mActiveAudioOutDevice != null || (getConnectionState(previousActiveOutDevice) == BluetoothProfile.STATE_CONNECTED); Loading @@ -1239,8 +1234,11 @@ public class LeAudioService extends ProfileService { /** * Set the active device group. * * @param hasFallbackDevice hasFallbackDevice whether any fallback device exists when * {@code device} is null. */ private void setActiveGroupWithDevice(BluetoothDevice device) { private void setActiveGroupWithDevice(BluetoothDevice device, boolean hasFallbackDevice) { int groupId = LE_AUDIO_GROUP_ID_INVALID; if (device != null) { Loading Loading @@ -1277,28 +1275,40 @@ public class LeAudioService extends ProfileService { * 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); handleGroupTransitToInactive(currentlyActiveGroupId, hasFallbackDevice); } } /** * Remove the current active group. * * @param hasFallbackDevice whether any fallback device exists when deactivating * the current active device. * @return true on success, otherwise false */ public boolean removeActiveDevice(boolean hasFallbackDevice) { /* Clear active group */ setActiveGroupWithDevice(null, hasFallbackDevice); return true; } /** * Set the active group represented by device. * * @param device the new active device * @param device the new active device. Should not be null. * @return true on success, otherwise false */ public boolean setActiveDevice(BluetoothDevice device) { /* Clear active group */ if (device == null) { setActiveGroupWithDevice(device); return true; Log.e(TAG, "device should not be null!"); return removeActiveDevice(false); } if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { Log.e(TAG, "setActiveDevice(" + device + "): failed because group device is not " + "connected"); return false; } setActiveGroupWithDevice(device); setActiveGroupWithDevice(device, false); return true; } Loading Loading @@ -1455,7 +1465,7 @@ public class LeAudioService extends ProfileService { } descriptor.mIsActive = updateActiveDevices(groupId, AUDIO_DIRECTION_NONE, descriptor.mDirection, true); descriptor.mDirection, true, false); if (descriptor.mIsActive) { notifyGroupStatusChanged(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE); Loading @@ -1464,7 +1474,7 @@ public class LeAudioService extends ProfileService { } } private void handleGroupTransitToInactive(int groupId) { private void handleGroupTransitToInactive(int groupId, boolean hasFallbackDevice) { synchronized (mGroupLock) { LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId); if (descriptor == null || !descriptor.mIsActive) { Loading @@ -1475,7 +1485,7 @@ public class LeAudioService extends ProfileService { descriptor.mIsActive = false; updateActiveDevices(groupId, descriptor.mDirection, AUDIO_DIRECTION_NONE, descriptor.mIsActive); descriptor.mIsActive, hasFallbackDevice); /* Clear lost devices */ if (DBG) Log.d(TAG, "Clear for group: " + groupId); clearLostDevicesWhileStreaming(descriptor); Loading Loading @@ -1725,7 +1735,7 @@ public class LeAudioService extends ProfileService { if (descriptor.mIsActive) { descriptor.mIsActive = updateActiveDevices(groupId, descriptor.mDirection, direction, descriptor.mIsActive); descriptor.mIsActive, false); if (!descriptor.mIsActive) { notifyGroupStatusChanged(groupId, BluetoothLeAudio.GROUP_STATUS_INACTIVE); Loading Loading @@ -1766,7 +1776,7 @@ public class LeAudioService extends ProfileService { break; } case LeAudioStackEvent.GROUP_STATUS_INACTIVE: { handleGroupTransitToInactive(groupId); handleGroupTransitToInactive(groupId, false); break; } case LeAudioStackEvent.GROUP_STATUS_TURNED_IDLE_DURING_CALL: { Loading Loading @@ -2007,21 +2017,16 @@ public class LeAudioService extends ProfileService { return result; } @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; } /** * Process a change for connection of a device. */ public synchronized void deviceConnected(BluetoothDevice device) { LeAudioDeviceDescriptor deviceDescriptor = getDeviceDescriptor(device); if (deviceDescriptor == null) { Log.e(TAG, "connectionStateChanged: No valid descriptor for device: " + device); Log.e(TAG, "deviceConnected: No valid descriptor for device: " + device); return; } if (toState == BluetoothProfile.STATE_CONNECTED) { if (deviceDescriptor.mGroupId == LE_AUDIO_GROUP_ID_INVALID || getConnectedPeerDevices(deviceDescriptor.mGroupId).size() == 1) { // Log LE Audio connection event if we are the first device in a set Loading @@ -2034,12 +2039,21 @@ public class LeAudioService extends ProfileService { if (descriptor != null) { descriptor.mIsConnected = true; } else { Log.e(TAG, "connectionStateChanged(STATE_CONNECTED): no descriptors for group: " Log.e(TAG, "deviceConnected: no descriptors for group: " + deviceDescriptor.mGroupId); } } // Check if the device is disconnected - if unbond, remove the state machine if (toState == BluetoothProfile.STATE_DISCONNECTED) { /** * Process a change for disconnection of a device. */ public synchronized void deviceDisconnected(BluetoothDevice device, boolean hasFallbackDevice) { LeAudioDeviceDescriptor deviceDescriptor = getDeviceDescriptor(device); if (deviceDescriptor == null) { Log.e(TAG, "deviceDisconnected: No valid descriptor for device: " + device); return; } int bondState = mAdapterService.getBondState(device); if (bondState == BluetoothDevice.BOND_NONE) { if (DBG) { Loading @@ -2051,8 +2065,8 @@ public class LeAudioService extends ProfileService { synchronized (mGroupLock) { LeAudioGroupDescriptor descriptor = getGroupDescriptor(deviceDescriptor.mGroupId); if (descriptor == null) { Log.e(TAG, "connectionStateChanged(STATE_DISCONNECTED): no descriptors for " + "group: " + deviceDescriptor.mGroupId); Log.e(TAG, "deviceDisconnected: no descriptors for group: " + deviceDescriptor.mGroupId); return; } Loading @@ -2069,13 +2083,14 @@ public class LeAudioService extends ProfileService { descriptor.mIsConnected = false; if (descriptor.mIsActive) { /* Notify Native layer */ setActiveDevice(null); removeActiveDevice(hasFallbackDevice); descriptor.mIsActive = false; /* Update audio framework */ updateActiveDevices(deviceDescriptor.mGroupId, descriptor.mDirection, descriptor.mDirection, descriptor.mIsActive); descriptor.mIsActive, hasFallbackDevice); return; } } Loading @@ -2084,23 +2099,9 @@ public class LeAudioService extends ProfileService { updateActiveDevices(deviceDescriptor.mGroupId, descriptor.mDirection, descriptor.mDirection, descriptor.mIsActive); } } } descriptor.mIsActive, hasFallbackDevice); } 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); } } Loading Loading @@ -2230,7 +2231,7 @@ public class LeAudioService extends ProfileService { } if (getActiveGroupId() != LE_AUDIO_GROUP_ID_INVALID) { mHfpHandoverDevice = hfpHandoverDevice; setActiveDevice(null); removeActiveDevice(true); } } Loading Loading @@ -2523,7 +2524,7 @@ public class LeAudioService extends ProfileService { */ if (Objects.equals(device, mActiveAudioOutDevice) || Objects.equals(device, mActiveAudioInDevice)) { handleGroupTransitToInactive(groupId); handleGroupTransitToInactive(groupId, false); } mGroupDescriptors.remove(groupId); } Loading Loading @@ -2967,15 +2968,18 @@ public class LeAudioService extends ProfileService { public void setActiveDevice(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver) { try { Objects.requireNonNull(device, "device cannot be null"); Objects.requireNonNull(source, "source cannot be null"); Objects.requireNonNull(receiver, "receiver cannot be null"); LeAudioService service = getService(source); boolean result = false; if (service != null) { if (device == null) { result = service.removeActiveDevice(true); } else { result = service.setActiveDevice(device); } } receiver.send(result); } catch (RuntimeException e) { receiver.propagateException(e); Loading
android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java +10 −7 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.bluetooth.btservice; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.any; import static org.mockito.Mockito.isNull; import static org.mockito.Mockito.never; Loading Loading @@ -127,6 +128,7 @@ public class ActiveDeviceManagerTest { when(mHeadsetService.setActiveDevice(any())).thenReturn(true); when(mHearingAidService.setActiveDevice(any())).thenReturn(true); when(mLeAudioService.setActiveDevice(any())).thenReturn(true); when(mLeAudioService.removeActiveDevice(anyBoolean())).thenReturn(true); List<BluetoothDevice> connectedHearingAidDevices = new ArrayList<>(); connectedHearingAidDevices.add(mHearingAidDevice); Loading Loading @@ -443,7 +445,7 @@ public class ActiveDeviceManagerTest { verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice); leAudioDisconnected(mLeAudioDevice); verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(isNull()); verify(mLeAudioService, timeout(TIMEOUT_MS)).removeActiveDevice(false); } /** Loading @@ -457,10 +459,11 @@ public class ActiveDeviceManagerTest { leAudioConnected(mSecondaryAudioDevice); verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice); Mockito.clearInvocations(mLeAudioService); leAudioActiveDeviceChanged(mLeAudioDevice); // Don't call mLeAudioService.setActiveDevice() TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper()); verify(mLeAudioService, times(1)).setActiveDevice(mLeAudioDevice); verify(mLeAudioService, never()).setActiveDevice(any(BluetoothDevice.class)); Assert.assertEquals(mLeAudioDevice, mActiveDeviceManager.getLeAudioActiveDevice()); } Loading Loading @@ -520,10 +523,10 @@ public class ActiveDeviceManagerTest { a2dpActiveDeviceChanged(mA2dpDevice); TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper()); verify(mLeAudioService).setActiveDevice(isNull()); verify(mLeAudioService).removeActiveDevice(true); verify(mA2dpService).setActiveDevice(mA2dpDevice); Assert.assertEquals(mA2dpDevice, mActiveDeviceManager.getA2dpActiveDevice()); Assert.assertEquals(null, mActiveDeviceManager.getLeAudioActiveDevice()); Assert.assertNull(mActiveDeviceManager.getLeAudioActiveDevice()); } /** Loading @@ -536,10 +539,10 @@ public class ActiveDeviceManagerTest { headsetActiveDeviceChanged(mHeadsetDevice); TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper()); verify(mLeAudioService).setActiveDevice(isNull()); verify(mLeAudioService).removeActiveDevice(true); verify(mHeadsetService).setActiveDevice(mHeadsetDevice); Assert.assertEquals(mHeadsetDevice, mActiveDeviceManager.getHfpActiveDevice()); Assert.assertEquals(null, mActiveDeviceManager.getLeAudioActiveDevice()); Assert.assertNull(mActiveDeviceManager.getLeAudioActiveDevice()); } /** Loading Loading @@ -578,7 +581,7 @@ public class ActiveDeviceManagerTest { Mockito.clearInvocations(mA2dpService); leAudioDisconnected(mLeAudioDevice); verify(mLeAudioService, timeout(TIMEOUT_MS).atLeast(1)).setActiveDevice(isNull()); verify(mLeAudioService, timeout(TIMEOUT_MS).atLeast(1)).removeActiveDevice(true); verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice); } Loading
android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBinderTest.java +9 −0 Original line number Diff line number Diff line Loading @@ -148,6 +148,15 @@ public class LeAudioBinderTest { verify(mMockService).setActiveDevice(device); } @Test public void setActiveDevice_withNullDevice_callsRemoveActiveDevice() { AttributionSource source = new AttributionSource.Builder(0).build(); final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); mBinder.setActiveDevice(null, source, recv); verify(mMockService).removeActiveDevice(true); } @Test public void getActiveDevices() { AttributionSource source = new AttributionSource.Builder(0).build(); Loading