Loading src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java +76 −30 Original line number Diff line number Diff line Loading @@ -28,7 +28,9 @@ import android.content.Context; import android.media.AudioManager; import android.media.AudioDeviceInfo; import android.media.audio.common.AudioDevice; import android.os.Bundle; import android.telecom.Log; import android.util.ArraySet; import android.util.LocalLog; import com.android.internal.util.IndentingPrintWriter; Loading Loading @@ -234,18 +236,38 @@ public class BluetoothDeviceManager { } public int getNumConnectedDevices() { synchronized (mLock) { return mHfpDevicesByAddress.size() + mHearingAidDevicesByAddress.size() + getLeAudioConnectedDevices().size(); } return getConnectedDevices().size(); } public Collection<BluetoothDevice> getConnectedDevices() { synchronized (mLock) { ArrayList<BluetoothDevice> result = new ArrayList<>(mHfpDevicesByAddress.values()); ArraySet<BluetoothDevice> result = new ArraySet<>(); // Set storing the group ids of all dual mode audio devices to de-dupe them Set<Integer> dualModeGroupIds = new ArraySet<>(); for (BluetoothDevice hfpDevice: mHfpDevicesByAddress.values()) { result.add(hfpDevice); if (mBluetoothLeAudioService == null) { continue; } int groupId = mBluetoothLeAudioService.getGroupId(hfpDevice); if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) { dualModeGroupIds.add(groupId); } } result.addAll(mHearingAidDevicesByAddress.values()); result.addAll(getLeAudioConnectedDevices()); if (mBluetoothLeAudioService == null) { return Collections.unmodifiableCollection(result); } for (BluetoothDevice leAudioDevice: getLeAudioConnectedDevices()) { // Exclude dual mode audio devices included from the HFP devices list int groupId = mBluetoothLeAudioService.getGroupId(leAudioDevice); if (groupId != BluetoothLeAudio.GROUP_ID_INVALID && !dualModeGroupIds.contains(groupId)) { result.add(leAudioDevice); } } return Collections.unmodifiableCollection(result); } } Loading @@ -253,9 +275,9 @@ public class BluetoothDeviceManager { // Same as getConnectedDevices except it filters out the hearing aid devices that are linked // together by their hiSyncId. public Collection<BluetoothDevice> getUniqueConnectedDevices() { ArrayList<BluetoothDevice> result; ArraySet<BluetoothDevice> result; synchronized (mLock) { result = new ArrayList<>(mHfpDevicesByAddress.values()); result = new ArraySet<>(mHfpDevicesByAddress.values()); } Set<Long> seenHiSyncIds = new LinkedHashSet<>(); // Add the left-most active device to the seen list so that we match up with the list Loading Loading @@ -367,6 +389,8 @@ public class BluetoothDeviceManager { return; } if (!targetDeviceMap.containsKey(device.getAddress())) { Log.i(this, "Adding device with address: " + device + " and devicetype=" + getDeviceTypeString(deviceType)); targetDeviceMap.put(device.getAddress(), device); mBluetoothRouteManager.onDeviceAdded(device.getAddress()); } Loading @@ -391,6 +415,8 @@ public class BluetoothDeviceManager { return; } if (targetDeviceMap.containsKey(device.getAddress())) { Log.i(this, "Removing device with address: " + device + " and devicetype=" + getDeviceTypeString(deviceType)); targetDeviceMap.remove(device.getAddress()); mBluetoothRouteManager.onDeviceLost(device.getAddress()); } Loading Loading @@ -568,50 +594,70 @@ public class BluetoothDeviceManager { // Connect audio to the bluetooth device at address, checking to see whether it's // le audio, hearing aid or a HFP device, and using the proper BT API. public boolean connectAudio(String address, boolean switchingBtDevices) { int callProfile = BluetoothProfile.LE_AUDIO; Log.i(this, "Telecomm connecting audio to device: " + address); BluetoothDevice device = null; if (mLeAudioDevicesByAddress.containsKey(address)) { Log.i(this, "Telecomm found LE Audio device for address: " + address); if (mBluetoothLeAudioService == null) { Log.w(this, "Attempting to turn on audio when the le audio service is null"); return false; } BluetoothDevice device = mLeAudioDevicesByAddress.get(address); device = mLeAudioDevicesByAddress.get(address); callProfile = BluetoothProfile.LE_AUDIO; } else if (mHearingAidDevicesByAddress.containsKey(address)) { Log.i(this, "Telecomm found hearing aid device for address: " + address); if (mBluetoothHearingAid == null) { Log.w(this, "Attempting to turn on audio when the hearing aid service is null"); return false; } device = mHearingAidDevicesByAddress.get(address); callProfile = BluetoothProfile.HEARING_AID; } else if (mHfpDevicesByAddress.containsKey(address)) { Log.i(this, "Telecomm found HFP device for address: " + address); if (mBluetoothHeadset == null) { Log.w(this, "Attempting to turn on audio when the headset service is null"); return false; } device = mHfpDevicesByAddress.get(address); callProfile = BluetoothProfile.HEADSET; } if (device == null) { Log.w(this, "No active profiles for Bluetooth address=" + address); return false; } Bundle preferredAudioProfiles = mBluetoothAdapter.getPreferredAudioProfiles(device); if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty() && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX) != 0) { Log.i(this, "Preferred duplex profile for device=" + address + " is " + preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)); callProfile = preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX); } if (callProfile == BluetoothProfile.LE_AUDIO) { if (mBluetoothAdapter.setActiveDevice( device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) { /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device. * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that * will be audio switched to is available to be choose as communication device */ if (!switchingBtDevices) { return setLeAudioCommunicationDevice(); } return true; } return false; } else if (mHearingAidDevicesByAddress.containsKey(address)) { if (mBluetoothHearingAid == null) { Log.w(this, "Attempting to turn on audio when the hearing aid service is null"); return false; } if (mBluetoothAdapter.setActiveDevice( mHearingAidDevicesByAddress.get(address), BluetoothAdapter.ACTIVE_DEVICE_ALL)) { } else if (callProfile == BluetoothProfile.HEARING_AID) { if (mBluetoothAdapter.setActiveDevice(device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) { /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device. * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that * will be audio switched to is available to be choose as communication device */ if (!switchingBtDevices) { return setHearingAidCommunicationDevice(); } return true; } return false; } else if (mHfpDevicesByAddress.containsKey(address)) { BluetoothDevice device = mHfpDevicesByAddress.get(address); if (mBluetoothHeadset == null) { Log.w(this, "Attempting to turn on audio when the headset service is null"); return false; } } else if (callProfile == BluetoothProfile.HEADSET) { boolean success = mBluetoothAdapter.setActiveDevice(device, BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL); if (!success) { Loading src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java +26 −4 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.telecom.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothHearingAid; Loading @@ -25,6 +26,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.telecom.Log; import android.telecom.Logging.Session; Loading Loading @@ -84,7 +86,7 @@ public class BluetoothStateReceiver extends BroadcastReceiver { intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED); BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class); if (device == null) { Log.w(LOG_TAG, "Got null device from broadcast. " + "Ignoring."); Loading Loading @@ -115,7 +117,7 @@ public class BluetoothStateReceiver extends BroadcastReceiver { int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_DISCONNECTED); BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class); if (device == null) { Log.w(LOG_TAG, "Got null device from broadcast. " + Loading Loading @@ -149,7 +151,7 @@ public class BluetoothStateReceiver extends BroadcastReceiver { private void handleActiveDeviceChanged(Intent intent) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class); int deviceType; if (BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) { Loading Loading @@ -181,11 +183,31 @@ public class BluetoothStateReceiver extends BroadcastReceiver { } args.arg2 = device.getAddress(); boolean usePreferredAudioProfile = false; BluetoothAdapter bluetoothAdapter = mBluetoothDeviceManager.getBluetoothAdapter(); int preferredDuplexProfile = BluetoothProfile.LE_AUDIO; if (bluetoothAdapter != null) { Bundle preferredAudioProfiles = bluetoothAdapter.getPreferredAudioProfiles( device); if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty() && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX) != 0) { Log.i(this, "Preferred duplex profile for device=" + device + " is " + preferredAudioProfiles.getInt( BluetoothAdapter.AUDIO_MODE_DUPLEX)); usePreferredAudioProfile = true; preferredDuplexProfile = preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX); } } if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) { /* In Le Audio case, once device got Active, the Telecom needs to make sure it * is set as communication device before we can say that BT_AUDIO_IS_ON */ if (!mBluetoothDeviceManager.setLeAudioCommunicationDevice()) { if ((!usePreferredAudioProfile || preferredDuplexProfile == BluetoothProfile.LE_AUDIO) && !mBluetoothDeviceManager.setLeAudioCommunicationDevice()) { Log.w(LOG_TAG, "Device %s cannot be use as LE audio communication device.", device); Loading tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java +104 −3 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.content.BroadcastReceiver; import android.content.Intent; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.os.Bundle; import android.os.Parcel; import android.test.suitebuilder.annotation.SmallTest; Loading Loading @@ -178,6 +179,7 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase { buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO)); leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1); when(mBluetoothLeAudio.getGroupId(device5)).thenReturn(1); when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device5); receiverUnderTest.onReceive(mContext, Loading @@ -188,6 +190,7 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase { BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO)); leAudioCallbacksTest.getValue().onGroupNodeAdded(device6, 2); when(mBluetoothLeAudio.getConnectedGroupLeadDevice(2)).thenReturn(device6); when(mBluetoothLeAudio.getGroupId(device6)).thenReturn(1); receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3, BluetoothDeviceManager.DEVICE_TYPE_HEADSET)); Loading Loading @@ -263,17 +266,19 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase { @Test public void testLeAudioDedup() { receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, buildConnectionActionIntent(BluetoothProfile.STATE_CONNECTED, device1, BluetoothDeviceManager.DEVICE_TYPE_HEADSET)); receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5, buildConnectionActionIntent(BluetoothProfile.STATE_CONNECTED, device5, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO)); leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1); receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device6, buildConnectionActionIntent(BluetoothProfile.STATE_CONNECTED, device6, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO)); leAudioCallbacksTest.getValue().onGroupNodeAdded(device6, 1); when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device5); when(mBluetoothLeAudio.getGroupId(device5)).thenReturn(1); when(mBluetoothLeAudio.getGroupId(device6)).thenReturn(1); assertEquals(2, mBluetoothDeviceManager.getNumConnectedDevices()); assertEquals(2, mBluetoothDeviceManager.getUniqueConnectedDevices().size()); } Loading Loading @@ -458,6 +463,8 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase { verify(mBluetoothHeadset, never()).connectAudio(); verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL)); verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_AUDIO)); receiverUnderTest.onReceive(mContext, buildActiveDeviceChangeActionIntent(device5, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO)); Loading Loading @@ -485,6 +492,8 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase { verify(mBluetoothHeadset, never()).connectAudio(); verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL)); verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL)); when(mAdapter.getActiveDevices(eq(BluetoothProfile.LE_AUDIO))) .thenReturn(Arrays.asList(device5, device6)); Loading @@ -497,6 +506,98 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase { verify(mAdapter).setActiveDevice(device6, BluetoothAdapter.ACTIVE_DEVICE_ALL); } @SmallTest @Test public void testConnectDualModeEarbud() { receiverUnderTest.setIsInCall(true); // LE Audio earbuds connected receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO)); leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1); receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothLeAudio.STATE_CONNECTED, device6, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO)); leAudioCallbacksTest.getValue().onGroupNodeAdded(device6, 1); // HFP device connected receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5, BluetoothDeviceManager.DEVICE_TYPE_HEADSET)); when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true); AudioDeviceInfo mockAudioDevice5Info = mock(AudioDeviceInfo.class); when(mockAudioDevice5Info.getAddress()).thenReturn(device5.getAddress()); when(mockAudioDevice5Info.getType()).thenReturn(AudioDeviceInfo.TYPE_BLE_HEADSET); AudioDeviceInfo mockAudioDevice6Info = mock(AudioDeviceInfo.class); when(mockAudioDevice6Info.getAddress()).thenReturn(device6.getAddress()); when(mockAudioDevice6Info.getType()).thenReturn(AudioDeviceInfo.TYPE_BLE_HEADSET); List<AudioDeviceInfo> devices = new ArrayList<>(); devices.add(mockAudioDevice5Info); devices.add(mockAudioDevice6Info); when(mockAudioManager.getAvailableCommunicationDevices()) .thenReturn(devices); when(mockAudioManager.setCommunicationDevice(mockAudioDevice5Info)) .thenReturn(true); Bundle hfpPreferred = new Bundle(); hfpPreferred.putInt(BluetoothAdapter.AUDIO_MODE_DUPLEX, BluetoothProfile.HEADSET); Bundle leAudioPreferred = new Bundle(); leAudioPreferred.putInt(BluetoothAdapter.AUDIO_MODE_DUPLEX, BluetoothProfile.LE_AUDIO); // TEST 1: LE Audio preferred for DUPLEX when(mAdapter.getPreferredAudioProfiles(device5)).thenReturn(leAudioPreferred); when(mAdapter.getPreferredAudioProfiles(device6)).thenReturn(leAudioPreferred); mBluetoothDeviceManager.connectAudio(device5.getAddress(), false); verify(mAdapter, times(1)).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_ALL); verify(mBluetoothHeadset, never()).connectAudio(); verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL)); verify(mockAudioManager).setCommunicationDevice(mockAudioDevice5Info); when(mAdapter.getActiveDevices(eq(BluetoothProfile.LE_AUDIO))) .thenReturn(Arrays.asList(device5, device6)); // Check disconnect during a call devices.remove(mockAudioDevice5Info); receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device5, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO)); leAudioCallbacksTest.getValue().onGroupNodeRemoved(device5, 1); mBluetoothDeviceManager.connectAudio(device6.getAddress(), false); verify(mAdapter).setActiveDevice(device6, BluetoothAdapter.ACTIVE_DEVICE_ALL); verify(mBluetoothHeadset, never()).connectAudio(); verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL)); // Reconnect other LE Audio earbud devices.add(mockAudioDevice5Info); receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO)); leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1); // Disconnects audio mBluetoothDeviceManager.disconnectAudio(); verify(mockAudioManager, times(1)).clearCommunicationDevice(); verify(mBluetoothHeadset, times(1)).disconnectAudio(); // TEST 2: HFP preferred for DUPLEX when(mAdapter.getPreferredAudioProfiles(device5)).thenReturn(hfpPreferred); when(mAdapter.getPreferredAudioProfiles(device6)).thenReturn(hfpPreferred); when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL))).thenReturn(true); mBluetoothDeviceManager.connectAudio(device5.getAddress(), false); verify(mAdapter).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL); verify(mAdapter, times(1)).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_ALL); verify(mBluetoothHeadset).connectAudio(); mBluetoothDeviceManager.disconnectAudio(); verify(mBluetoothHeadset, times(2)).disconnectAudio(); } @SmallTest @Test public void testClearHearingAidCommunicationDevice() { Loading Loading
src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java +76 −30 Original line number Diff line number Diff line Loading @@ -28,7 +28,9 @@ import android.content.Context; import android.media.AudioManager; import android.media.AudioDeviceInfo; import android.media.audio.common.AudioDevice; import android.os.Bundle; import android.telecom.Log; import android.util.ArraySet; import android.util.LocalLog; import com.android.internal.util.IndentingPrintWriter; Loading Loading @@ -234,18 +236,38 @@ public class BluetoothDeviceManager { } public int getNumConnectedDevices() { synchronized (mLock) { return mHfpDevicesByAddress.size() + mHearingAidDevicesByAddress.size() + getLeAudioConnectedDevices().size(); } return getConnectedDevices().size(); } public Collection<BluetoothDevice> getConnectedDevices() { synchronized (mLock) { ArrayList<BluetoothDevice> result = new ArrayList<>(mHfpDevicesByAddress.values()); ArraySet<BluetoothDevice> result = new ArraySet<>(); // Set storing the group ids of all dual mode audio devices to de-dupe them Set<Integer> dualModeGroupIds = new ArraySet<>(); for (BluetoothDevice hfpDevice: mHfpDevicesByAddress.values()) { result.add(hfpDevice); if (mBluetoothLeAudioService == null) { continue; } int groupId = mBluetoothLeAudioService.getGroupId(hfpDevice); if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) { dualModeGroupIds.add(groupId); } } result.addAll(mHearingAidDevicesByAddress.values()); result.addAll(getLeAudioConnectedDevices()); if (mBluetoothLeAudioService == null) { return Collections.unmodifiableCollection(result); } for (BluetoothDevice leAudioDevice: getLeAudioConnectedDevices()) { // Exclude dual mode audio devices included from the HFP devices list int groupId = mBluetoothLeAudioService.getGroupId(leAudioDevice); if (groupId != BluetoothLeAudio.GROUP_ID_INVALID && !dualModeGroupIds.contains(groupId)) { result.add(leAudioDevice); } } return Collections.unmodifiableCollection(result); } } Loading @@ -253,9 +275,9 @@ public class BluetoothDeviceManager { // Same as getConnectedDevices except it filters out the hearing aid devices that are linked // together by their hiSyncId. public Collection<BluetoothDevice> getUniqueConnectedDevices() { ArrayList<BluetoothDevice> result; ArraySet<BluetoothDevice> result; synchronized (mLock) { result = new ArrayList<>(mHfpDevicesByAddress.values()); result = new ArraySet<>(mHfpDevicesByAddress.values()); } Set<Long> seenHiSyncIds = new LinkedHashSet<>(); // Add the left-most active device to the seen list so that we match up with the list Loading Loading @@ -367,6 +389,8 @@ public class BluetoothDeviceManager { return; } if (!targetDeviceMap.containsKey(device.getAddress())) { Log.i(this, "Adding device with address: " + device + " and devicetype=" + getDeviceTypeString(deviceType)); targetDeviceMap.put(device.getAddress(), device); mBluetoothRouteManager.onDeviceAdded(device.getAddress()); } Loading @@ -391,6 +415,8 @@ public class BluetoothDeviceManager { return; } if (targetDeviceMap.containsKey(device.getAddress())) { Log.i(this, "Removing device with address: " + device + " and devicetype=" + getDeviceTypeString(deviceType)); targetDeviceMap.remove(device.getAddress()); mBluetoothRouteManager.onDeviceLost(device.getAddress()); } Loading Loading @@ -568,50 +594,70 @@ public class BluetoothDeviceManager { // Connect audio to the bluetooth device at address, checking to see whether it's // le audio, hearing aid or a HFP device, and using the proper BT API. public boolean connectAudio(String address, boolean switchingBtDevices) { int callProfile = BluetoothProfile.LE_AUDIO; Log.i(this, "Telecomm connecting audio to device: " + address); BluetoothDevice device = null; if (mLeAudioDevicesByAddress.containsKey(address)) { Log.i(this, "Telecomm found LE Audio device for address: " + address); if (mBluetoothLeAudioService == null) { Log.w(this, "Attempting to turn on audio when the le audio service is null"); return false; } BluetoothDevice device = mLeAudioDevicesByAddress.get(address); device = mLeAudioDevicesByAddress.get(address); callProfile = BluetoothProfile.LE_AUDIO; } else if (mHearingAidDevicesByAddress.containsKey(address)) { Log.i(this, "Telecomm found hearing aid device for address: " + address); if (mBluetoothHearingAid == null) { Log.w(this, "Attempting to turn on audio when the hearing aid service is null"); return false; } device = mHearingAidDevicesByAddress.get(address); callProfile = BluetoothProfile.HEARING_AID; } else if (mHfpDevicesByAddress.containsKey(address)) { Log.i(this, "Telecomm found HFP device for address: " + address); if (mBluetoothHeadset == null) { Log.w(this, "Attempting to turn on audio when the headset service is null"); return false; } device = mHfpDevicesByAddress.get(address); callProfile = BluetoothProfile.HEADSET; } if (device == null) { Log.w(this, "No active profiles for Bluetooth address=" + address); return false; } Bundle preferredAudioProfiles = mBluetoothAdapter.getPreferredAudioProfiles(device); if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty() && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX) != 0) { Log.i(this, "Preferred duplex profile for device=" + address + " is " + preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)); callProfile = preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX); } if (callProfile == BluetoothProfile.LE_AUDIO) { if (mBluetoothAdapter.setActiveDevice( device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) { /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device. * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that * will be audio switched to is available to be choose as communication device */ if (!switchingBtDevices) { return setLeAudioCommunicationDevice(); } return true; } return false; } else if (mHearingAidDevicesByAddress.containsKey(address)) { if (mBluetoothHearingAid == null) { Log.w(this, "Attempting to turn on audio when the hearing aid service is null"); return false; } if (mBluetoothAdapter.setActiveDevice( mHearingAidDevicesByAddress.get(address), BluetoothAdapter.ACTIVE_DEVICE_ALL)) { } else if (callProfile == BluetoothProfile.HEARING_AID) { if (mBluetoothAdapter.setActiveDevice(device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) { /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device. * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that * will be audio switched to is available to be choose as communication device */ if (!switchingBtDevices) { return setHearingAidCommunicationDevice(); } return true; } return false; } else if (mHfpDevicesByAddress.containsKey(address)) { BluetoothDevice device = mHfpDevicesByAddress.get(address); if (mBluetoothHeadset == null) { Log.w(this, "Attempting to turn on audio when the headset service is null"); return false; } } else if (callProfile == BluetoothProfile.HEADSET) { boolean success = mBluetoothAdapter.setActiveDevice(device, BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL); if (!success) { Loading
src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java +26 −4 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.telecom.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothHearingAid; Loading @@ -25,6 +26,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.telecom.Log; import android.telecom.Logging.Session; Loading Loading @@ -84,7 +86,7 @@ public class BluetoothStateReceiver extends BroadcastReceiver { intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED); BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class); if (device == null) { Log.w(LOG_TAG, "Got null device from broadcast. " + "Ignoring."); Loading Loading @@ -115,7 +117,7 @@ public class BluetoothStateReceiver extends BroadcastReceiver { int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_DISCONNECTED); BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class); if (device == null) { Log.w(LOG_TAG, "Got null device from broadcast. " + Loading Loading @@ -149,7 +151,7 @@ public class BluetoothStateReceiver extends BroadcastReceiver { private void handleActiveDeviceChanged(Intent intent) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class); int deviceType; if (BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) { Loading Loading @@ -181,11 +183,31 @@ public class BluetoothStateReceiver extends BroadcastReceiver { } args.arg2 = device.getAddress(); boolean usePreferredAudioProfile = false; BluetoothAdapter bluetoothAdapter = mBluetoothDeviceManager.getBluetoothAdapter(); int preferredDuplexProfile = BluetoothProfile.LE_AUDIO; if (bluetoothAdapter != null) { Bundle preferredAudioProfiles = bluetoothAdapter.getPreferredAudioProfiles( device); if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty() && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX) != 0) { Log.i(this, "Preferred duplex profile for device=" + device + " is " + preferredAudioProfiles.getInt( BluetoothAdapter.AUDIO_MODE_DUPLEX)); usePreferredAudioProfile = true; preferredDuplexProfile = preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX); } } if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) { /* In Le Audio case, once device got Active, the Telecom needs to make sure it * is set as communication device before we can say that BT_AUDIO_IS_ON */ if (!mBluetoothDeviceManager.setLeAudioCommunicationDevice()) { if ((!usePreferredAudioProfile || preferredDuplexProfile == BluetoothProfile.LE_AUDIO) && !mBluetoothDeviceManager.setLeAudioCommunicationDevice()) { Log.w(LOG_TAG, "Device %s cannot be use as LE audio communication device.", device); Loading
tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java +104 −3 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.content.BroadcastReceiver; import android.content.Intent; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.os.Bundle; import android.os.Parcel; import android.test.suitebuilder.annotation.SmallTest; Loading Loading @@ -178,6 +179,7 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase { buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO)); leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1); when(mBluetoothLeAudio.getGroupId(device5)).thenReturn(1); when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device5); receiverUnderTest.onReceive(mContext, Loading @@ -188,6 +190,7 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase { BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO)); leAudioCallbacksTest.getValue().onGroupNodeAdded(device6, 2); when(mBluetoothLeAudio.getConnectedGroupLeadDevice(2)).thenReturn(device6); when(mBluetoothLeAudio.getGroupId(device6)).thenReturn(1); receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3, BluetoothDeviceManager.DEVICE_TYPE_HEADSET)); Loading Loading @@ -263,17 +266,19 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase { @Test public void testLeAudioDedup() { receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, buildConnectionActionIntent(BluetoothProfile.STATE_CONNECTED, device1, BluetoothDeviceManager.DEVICE_TYPE_HEADSET)); receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5, buildConnectionActionIntent(BluetoothProfile.STATE_CONNECTED, device5, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO)); leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1); receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device6, buildConnectionActionIntent(BluetoothProfile.STATE_CONNECTED, device6, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO)); leAudioCallbacksTest.getValue().onGroupNodeAdded(device6, 1); when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device5); when(mBluetoothLeAudio.getGroupId(device5)).thenReturn(1); when(mBluetoothLeAudio.getGroupId(device6)).thenReturn(1); assertEquals(2, mBluetoothDeviceManager.getNumConnectedDevices()); assertEquals(2, mBluetoothDeviceManager.getUniqueConnectedDevices().size()); } Loading Loading @@ -458,6 +463,8 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase { verify(mBluetoothHeadset, never()).connectAudio(); verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL)); verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_AUDIO)); receiverUnderTest.onReceive(mContext, buildActiveDeviceChangeActionIntent(device5, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO)); Loading Loading @@ -485,6 +492,8 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase { verify(mBluetoothHeadset, never()).connectAudio(); verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL)); verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL)); when(mAdapter.getActiveDevices(eq(BluetoothProfile.LE_AUDIO))) .thenReturn(Arrays.asList(device5, device6)); Loading @@ -497,6 +506,98 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase { verify(mAdapter).setActiveDevice(device6, BluetoothAdapter.ACTIVE_DEVICE_ALL); } @SmallTest @Test public void testConnectDualModeEarbud() { receiverUnderTest.setIsInCall(true); // LE Audio earbuds connected receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO)); leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1); receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothLeAudio.STATE_CONNECTED, device6, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO)); leAudioCallbacksTest.getValue().onGroupNodeAdded(device6, 1); // HFP device connected receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5, BluetoothDeviceManager.DEVICE_TYPE_HEADSET)); when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true); AudioDeviceInfo mockAudioDevice5Info = mock(AudioDeviceInfo.class); when(mockAudioDevice5Info.getAddress()).thenReturn(device5.getAddress()); when(mockAudioDevice5Info.getType()).thenReturn(AudioDeviceInfo.TYPE_BLE_HEADSET); AudioDeviceInfo mockAudioDevice6Info = mock(AudioDeviceInfo.class); when(mockAudioDevice6Info.getAddress()).thenReturn(device6.getAddress()); when(mockAudioDevice6Info.getType()).thenReturn(AudioDeviceInfo.TYPE_BLE_HEADSET); List<AudioDeviceInfo> devices = new ArrayList<>(); devices.add(mockAudioDevice5Info); devices.add(mockAudioDevice6Info); when(mockAudioManager.getAvailableCommunicationDevices()) .thenReturn(devices); when(mockAudioManager.setCommunicationDevice(mockAudioDevice5Info)) .thenReturn(true); Bundle hfpPreferred = new Bundle(); hfpPreferred.putInt(BluetoothAdapter.AUDIO_MODE_DUPLEX, BluetoothProfile.HEADSET); Bundle leAudioPreferred = new Bundle(); leAudioPreferred.putInt(BluetoothAdapter.AUDIO_MODE_DUPLEX, BluetoothProfile.LE_AUDIO); // TEST 1: LE Audio preferred for DUPLEX when(mAdapter.getPreferredAudioProfiles(device5)).thenReturn(leAudioPreferred); when(mAdapter.getPreferredAudioProfiles(device6)).thenReturn(leAudioPreferred); mBluetoothDeviceManager.connectAudio(device5.getAddress(), false); verify(mAdapter, times(1)).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_ALL); verify(mBluetoothHeadset, never()).connectAudio(); verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL)); verify(mockAudioManager).setCommunicationDevice(mockAudioDevice5Info); when(mAdapter.getActiveDevices(eq(BluetoothProfile.LE_AUDIO))) .thenReturn(Arrays.asList(device5, device6)); // Check disconnect during a call devices.remove(mockAudioDevice5Info); receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device5, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO)); leAudioCallbacksTest.getValue().onGroupNodeRemoved(device5, 1); mBluetoothDeviceManager.connectAudio(device6.getAddress(), false); verify(mAdapter).setActiveDevice(device6, BluetoothAdapter.ACTIVE_DEVICE_ALL); verify(mBluetoothHeadset, never()).connectAudio(); verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL)); // Reconnect other LE Audio earbud devices.add(mockAudioDevice5Info); receiverUnderTest.onReceive(mContext, buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO)); leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1); // Disconnects audio mBluetoothDeviceManager.disconnectAudio(); verify(mockAudioManager, times(1)).clearCommunicationDevice(); verify(mBluetoothHeadset, times(1)).disconnectAudio(); // TEST 2: HFP preferred for DUPLEX when(mAdapter.getPreferredAudioProfiles(device5)).thenReturn(hfpPreferred); when(mAdapter.getPreferredAudioProfiles(device6)).thenReturn(hfpPreferred); when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL))).thenReturn(true); mBluetoothDeviceManager.connectAudio(device5.getAddress(), false); verify(mAdapter).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL); verify(mAdapter, times(1)).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_ALL); verify(mBluetoothHeadset).connectAudio(); mBluetoothDeviceManager.disconnectAudio(); verify(mBluetoothHeadset, times(2)).disconnectAudio(); } @SmallTest @Test public void testClearHearingAidCommunicationDevice() { Loading