Add support for Generic Telephone Bearer service (GTBS)
This patch integrates Telecomm service to GTBS
Bug: 150670922
Tag: #feature
Sponsor: jpawlowski@
Test: atest BluetoothInstrumentationTests TelecomUnitTests
Change-Id: I996cd5ff980a7a7e5fbfb05e18567264651f8935
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 0776e9d..12481af 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -315,6 +315,9 @@
<intent-filter>
<action android:name="android.bluetooth.IBluetoothHeadsetPhone"/>
</intent-filter>
+ <intent-filter>
+ <action android:name="android.bluetooth.IBluetoothLeCallControlCallback" />
+ </intent-filter>
</service>
<service android:name=".components.TelecomService"
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index e5a6ecc..f6f710e 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -875,8 +875,8 @@
return HANDLED;
case SWITCH_FOCUS:
if (msg.arg1 == NO_FOCUS) {
- // Only disconnect SCO audio here instead of routing away from BT entirely.
- mBluetoothRouteManager.disconnectSco();
+ // Only disconnect audio here instead of routing away from BT entirely.
+ mBluetoothRouteManager.disconnectAudio();
reinitialize();
mCallAudioManager.notifyAudioOperationsComplete();
} else if (msg.arg1 == RINGING_FOCUS
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index c7671e3..cb31990 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -20,6 +20,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.telecom.Log;
@@ -37,6 +38,11 @@
import java.util.Set;
public class BluetoothDeviceManager {
+
+ public static final int DEVICE_TYPE_HEADSET = 0;
+ public static final int DEVICE_TYPE_HEARING_AID = 1;
+ public static final int DEVICE_TYPE_LE_AUDIO = 2;
+
private final BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
new BluetoothProfile.ServiceListener() {
@Override
@@ -52,6 +58,10 @@
mBluetoothHearingAid = (BluetoothHearingAid) proxy;
logString = "Got BluetoothHearingAid: "
+ mBluetoothHearingAid;
+ } else if (profile == BluetoothProfile.LE_AUDIO) {
+ mBluetoothLeAudioService = (BluetoothLeAudio) proxy;
+ logString = "Got BluetoothLeAudio: "
+ + mBluetoothLeAudioService;
} else {
logString = "Connected to non-requested bluetooth service." +
" Not changing bluetooth headset.";
@@ -74,7 +84,8 @@
if (profile == BluetoothProfile.HEADSET) {
mBluetoothHeadset = null;
lostServiceDevices = mHfpDevicesByAddress;
- mBluetoothRouteManager.onActiveDeviceChanged(null, false);
+ mBluetoothRouteManager.onActiveDeviceChanged(null,
+ DEVICE_TYPE_HEADSET);
logString = "Lost BluetoothHeadset service. " +
"Removing all tracked devices";
} else if (profile == BluetoothProfile.HEARING_AID) {
@@ -82,7 +93,15 @@
logString = "Lost BluetoothHearingAid service. " +
"Removing all tracked devices.";
lostServiceDevices = mHearingAidDevicesByAddress;
- mBluetoothRouteManager.onActiveDeviceChanged(null, true);
+ mBluetoothRouteManager.onActiveDeviceChanged(null,
+ DEVICE_TYPE_HEARING_AID);
+ } else if (profile == BluetoothProfile.LE_AUDIO) {
+ mBluetoothLeAudioService = null;
+ logString = "Lost BluetoothLeAudio service. " +
+ "Removing all tracked devices.";
+ lostServiceDevices = mLeAudioDevicesByAddress;
+ mBluetoothRouteManager.onActiveDeviceChanged(null,
+ DEVICE_TYPE_LE_AUDIO);
} else {
return;
}
@@ -108,6 +127,12 @@
new LinkedHashMap<>();
private final LinkedHashMap<BluetoothDevice, Long> mHearingAidDeviceSyncIds =
new LinkedHashMap<>();
+ private final LinkedHashMap<String, BluetoothDevice> mLeAudioDevicesByAddress =
+ new LinkedHashMap<>();
+ private final LinkedHashMap<BluetoothDevice, Integer> mGroupsByDevice =
+ new LinkedHashMap<>();
+ private int mGroupIdActive = BluetoothLeAudio.GROUP_ID_INVALID;
+ private int mGroupIdPending = BluetoothLeAudio.GROUP_ID_INVALID;
private final LocalLog mLocalLog = new LocalLog(20);
// This lock only protects internal state -- it doesn't lock on anything going into Telecom.
@@ -116,6 +141,7 @@
private BluetoothRouteManager mBluetoothRouteManager;
private BluetoothHeadset mBluetoothHeadset;
private BluetoothHearingAid mBluetoothHearingAid;
+ private BluetoothLeAudio mBluetoothLeAudioService;
private BluetoothDevice mBluetoothHearingAidActiveDeviceCache;
private BluetoothAdapter mBluetoothAdapter;
@@ -126,6 +152,8 @@
BluetoothProfile.HEADSET);
bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
BluetoothProfile.HEARING_AID);
+ bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
+ BluetoothProfile.LE_AUDIO);
}
}
@@ -133,9 +161,20 @@
mBluetoothRouteManager = brm;
}
+ private List<BluetoothDevice> getLeAudioConnectedDevices() {
+ synchronized (mLock) {
+ // Filter out disconnected devices and/or those that have no group assigned
+ ArrayList<BluetoothDevice> devices = new ArrayList<>(mGroupsByDevice.keySet());
+ devices.removeIf(device -> !mLeAudioDevicesByAddress.containsValue(device));
+ return devices;
+ }
+ }
+
public int getNumConnectedDevices() {
synchronized (mLock) {
- return mHfpDevicesByAddress.size() + mHearingAidDevicesByAddress.size();
+ return mHfpDevicesByAddress.size() +
+ mHearingAidDevicesByAddress.size() +
+ getLeAudioConnectedDevices().size();
}
}
@@ -143,6 +182,7 @@
synchronized (mLock) {
ArrayList<BluetoothDevice> result = new ArrayList<>(mHfpDevicesByAddress.values());
result.addAll(mHearingAidDevicesByAddress.values());
+ result.addAll(getLeAudioConnectedDevices());
return Collections.unmodifiableCollection(result);
}
}
@@ -177,6 +217,31 @@
seenHiSyncIds.add(hiSyncId);
}
}
+
+ Set<Integer> seenGroupIds = new LinkedHashSet<>();
+ if (mBluetoothAdapter != null) {
+ for (BluetoothDevice device : mBluetoothAdapter.getActiveDevices(
+ BluetoothProfile.LE_AUDIO)) {
+ if (device != null) {
+ result.add(device);
+ seenGroupIds.add(mGroupsByDevice.getOrDefault(device, -1));
+ break;
+ }
+ }
+ }
+ synchronized (mLock) {
+ for (BluetoothDevice d : getLeAudioConnectedDevices()) {
+ int groupId = mGroupsByDevice.getOrDefault(d,
+ BluetoothLeAudio.GROUP_ID_INVALID);
+ if (groupId == BluetoothLeAudio.GROUP_ID_INVALID
+ || seenGroupIds.contains(groupId)) {
+ continue;
+ }
+ result.add(d);
+ seenGroupIds.add(groupId);
+ }
+ }
+
return Collections.unmodifiableCollection(result);
}
@@ -192,6 +257,10 @@
return mBluetoothHearingAid;
}
+ public BluetoothLeAudio getLeAudioService() {
+ return mBluetoothLeAudioService;
+ }
+
public void setHeadsetServiceForTesting(BluetoothHeadset bluetoothHeadset) {
mBluetoothHeadset = bluetoothHeadset;
}
@@ -200,12 +269,33 @@
mBluetoothHearingAid = bluetoothHearingAid;
}
- void onDeviceConnected(BluetoothDevice device, boolean isHearingAid) {
- mLocalLog.log("Device connected -- address: " + device.getAddress() + " isHeadingAid: "
- + isHearingAid);
+ public void setLeAudioServiceForTesting(BluetoothLeAudio bluetoothLeAudio) {
+ mBluetoothLeAudioService = bluetoothLeAudio;
+ }
+
+ public static String getDeviceTypeString(int deviceType) {
+ switch (deviceType) {
+ case DEVICE_TYPE_LE_AUDIO:
+ return "LeAudio";
+ case DEVICE_TYPE_HEARING_AID:
+ return "HearingAid";
+ case DEVICE_TYPE_HEADSET:
+ return "HFP";
+ default:
+ return "unknown type";
+ }
+ }
+
+ void onDeviceConnected(BluetoothDevice device, int deviceType) {
synchronized (mLock) {
LinkedHashMap<String, BluetoothDevice> targetDeviceMap;
- if (isHearingAid) {
+ if (deviceType == DEVICE_TYPE_LE_AUDIO) {
+ if (mBluetoothLeAudioService == null) {
+ Log.w(this, "LE audio service null when receiving device added broadcast");
+ return;
+ }
+ targetDeviceMap = mLeAudioDevicesByAddress;
+ } else if (deviceType == DEVICE_TYPE_HEARING_AID) {
if (mBluetoothHearingAid == null) {
Log.w(this, "Hearing aid service null when receiving device added broadcast");
return;
@@ -213,12 +303,16 @@
long hiSyncId = mBluetoothHearingAid.getHiSyncId(device);
mHearingAidDeviceSyncIds.put(device, hiSyncId);
targetDeviceMap = mHearingAidDevicesByAddress;
- } else {
+ } else if (deviceType == DEVICE_TYPE_HEADSET) {
if (mBluetoothHeadset == null) {
Log.w(this, "Headset service null when receiving device added broadcast");
return;
}
targetDeviceMap = mHfpDevicesByAddress;
+ } else {
+ Log.w(this, "Device: " + device.getAddress() + " with invalid type: "
+ + getDeviceTypeString(deviceType));
+ return;
}
if (!targetDeviceMap.containsKey(device.getAddress())) {
targetDeviceMap.put(device.getAddress(), device);
@@ -227,16 +321,22 @@
}
}
- void onDeviceDisconnected(BluetoothDevice device, boolean isHearingAid) {
- mLocalLog.log("Device disconnected -- address: " + device.getAddress() + " isHeadingAid: "
- + isHearingAid);
+ void onDeviceDisconnected(BluetoothDevice device, int deviceType) {
+ mLocalLog.log("Device disconnected -- address: " + device.getAddress() + " deviceType: "
+ + deviceType);
synchronized (mLock) {
LinkedHashMap<String, BluetoothDevice> targetDeviceMap;
- if (isHearingAid) {
+ if (deviceType == DEVICE_TYPE_LE_AUDIO) {
+ targetDeviceMap = mLeAudioDevicesByAddress;
+ } else if (deviceType == DEVICE_TYPE_HEARING_AID) {
mHearingAidDeviceSyncIds.remove(device);
targetDeviceMap = mHearingAidDevicesByAddress;
- } else {
+ } else if (deviceType == DEVICE_TYPE_HEADSET) {
targetDeviceMap = mHfpDevicesByAddress;
+ } else {
+ Log.w(this, "Device: " + device.getAddress() + " with invalid type: "
+ + getDeviceTypeString(deviceType));
+ return;
}
if (targetDeviceMap.containsKey(device.getAddress())) {
targetDeviceMap.remove(device.getAddress());
@@ -245,16 +345,34 @@
}
}
+ void onGroupNodeAdded(BluetoothDevice device, int groupId) {
+ Log.i(this, device.getAddress() + " group added " + groupId);
+ if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
+ Log.w(this, "invalid parameter");
+ return;
+ }
+
+ synchronized (mLock) {
+ mGroupsByDevice.put(device, groupId);
+ }
+ }
+
+ void onGroupNodeRemoved(BluetoothDevice device, int groupId) {
+ if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
+ Log.w(this, "invalid parameter");
+ return;
+ }
+
+ synchronized (mLock) {
+ mGroupsByDevice.remove(device);
+ }
+ }
+
public void disconnectAudio() {
if (mBluetoothAdapter != null) {
- for (BluetoothDevice device: mBluetoothAdapter.getActiveDevices(
- BluetoothProfile.HEARING_AID)) {
- if (device != null) {
- mBluetoothAdapter.removeActiveDevice(BluetoothAdapter.ACTIVE_DEVICE_ALL);
- }
- }
+ mBluetoothAdapter.removeActiveDevice(BluetoothAdapter.ACTIVE_DEVICE_ALL);
+ disconnectSco();
}
- disconnectSco();
}
public void disconnectSco() {
@@ -265,10 +383,18 @@
}
}
- // Connect audio to the bluetooth device at address, checking to see whether it's a hearing aid
- // or a HFP device, and using the proper BT API.
+ // 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) {
- if (mHearingAidDevicesByAddress.containsKey(address)) {
+ if (mLeAudioDevicesByAddress.containsKey(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);
+ return mBluetoothAdapter.setActiveDevice(
+ device, BluetoothAdapter.ACTIVE_DEVICE_ALL);
+ } 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;
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index 81f4ab6..163dbb2 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -21,6 +21,7 @@
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothLeAudio;
import android.content.Context;
import android.os.Message;
import android.telecom.Log;
@@ -35,10 +36,12 @@
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.Timeouts;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -294,7 +297,7 @@
break;
case BT_AUDIO_IS_ON:
if (Objects.equals(mDeviceAddress, address)) {
- Log.i(LOG_TAG, "HFP connection success for device %s.", mDeviceAddress);
+ Log.i(LOG_TAG, "BT connection success for device %s.", mDeviceAddress);
transitionTo(mAudioConnectedStates.get(mDeviceAddress));
} else {
Log.w(LOG_TAG, "In connecting state for device %s but %s" +
@@ -451,6 +454,7 @@
// Tracks the active devices in the BT stack (HFP or hearing aid).
private BluetoothDevice mHfpActiveDeviceCache = null;
private BluetoothDevice mHearingAidActiveDeviceCache = null;
+ private BluetoothDevice mLeAudioDeviceCache = null;
private BluetoothDevice mMostRecentlyReportedActiveDevice = null;
public BluetoothRouteManager(Context context, TelecomSystem.SyncRoot lock,
@@ -548,8 +552,8 @@
sendMessage(DISCONNECT_HFP, args);
}
- public void disconnectSco() {
- mDeviceManager.disconnectSco();
+ public void disconnectAudio() {
+ mDeviceManager.disconnectAudio();
}
public void cacheHearingAidDevice() {
@@ -582,19 +586,37 @@
mListener.onBluetoothDeviceListChanged();
}
- public void onActiveDeviceChanged(BluetoothDevice device, boolean isHearingAid) {
- boolean wasActiveDevicePresent = mHearingAidActiveDeviceCache != null
- || mHfpActiveDeviceCache != null;
- if (isHearingAid) {
+ public void onAudioOn(String address) {
+ Session session = Log.createSubsession();
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = session;
+ args.arg2 = address;
+ sendMessage(BT_AUDIO_IS_ON, args);
+ }
+
+ public void onAudioLost(String address) {
+ Session session = Log.createSubsession();
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = session;
+ args.arg2 = address;
+ sendMessage(BT_AUDIO_LOST, args);
+ }
+
+ public void onActiveDeviceChanged(BluetoothDevice device, int deviceType) {
+ boolean wasActiveDevicePresent = hasBtActiveDevice();
+ if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) {
+ mLeAudioDeviceCache = device;
+ } else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID) {
mHearingAidActiveDeviceCache = device;
- } else {
+ } else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEADSET) {
mHfpActiveDeviceCache = device;
+ } else {
+ return;
}
if (device != null) mMostRecentlyReportedActiveDevice = device;
- boolean isActiveDevicePresent = mHearingAidActiveDeviceCache != null
- || mHfpActiveDeviceCache != null;
+ boolean isActiveDevicePresent = hasBtActiveDevice();
if (wasActiveDevicePresent && !isActiveDevicePresent) {
mListener.onBluetoothActiveDeviceGone();
@@ -604,7 +626,9 @@
}
public boolean hasBtActiveDevice() {
- return mHearingAidActiveDeviceCache != null || mHfpActiveDeviceCache != null;
+ return mLeAudioDeviceCache != null ||
+ mHearingAidActiveDeviceCache != null ||
+ mHfpActiveDeviceCache != null;
}
public Collection<BluetoothDevice> getConnectedDevices() {
@@ -682,6 +706,9 @@
if (mHearingAidActiveDeviceCache != null) {
return mHearingAidActiveDeviceCache.getAddress();
}
+ if (mLeAudioDeviceCache != null) {
+ return mLeAudioDeviceCache.getAddress();
+ }
return null;
}
@@ -705,29 +732,33 @@
BluetoothAdapter bluetoothAdapter = mDeviceManager.getBluetoothAdapter();
BluetoothHeadset bluetoothHeadset = mDeviceManager.getBluetoothHeadset();
BluetoothHearingAid bluetoothHearingAid = mDeviceManager.getBluetoothHearingAid();
+ BluetoothLeAudio bluetoothLeAudio = mDeviceManager.getLeAudioService();
BluetoothDevice hfpAudioOnDevice = null;
BluetoothDevice hearingAidActiveDevice = null;
+ BluetoothDevice leAudioActiveDevice = null;
if (bluetoothAdapter == null) {
Log.i(this, "getBluetoothAudioConnectedDevice: no adapter available.");
return null;
}
- if (bluetoothHeadset == null && bluetoothHearingAid == null) {
+ if (bluetoothHeadset == null && bluetoothHearingAid == null && bluetoothLeAudio == null) {
Log.i(this, "getBluetoothAudioConnectedDevice: no service available.");
return null;
}
+ int activeDevices = 0;
if (bluetoothHeadset != null) {
for (BluetoothDevice device : bluetoothAdapter.getActiveDevices(
BluetoothProfile.HEADSET)) {
hfpAudioOnDevice = device;
break;
}
-
if (bluetoothHeadset.getAudioState(hfpAudioOnDevice)
== BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
hfpAudioOnDevice = null;
+ } else {
+ activeDevices++;
}
}
@@ -736,22 +767,39 @@
BluetoothProfile.HEARING_AID)) {
if (device != null) {
hearingAidActiveDevice = device;
+ activeDevices++;
break;
}
}
}
- // Return the active device reported by either HFP or hearing aid. If both are reporting
- // active devices, go with the most recent one as reported by the receiver.
- if (hfpAudioOnDevice != null) {
- if (hearingAidActiveDevice != null) {
- Log.i(this, "Both HFP and hearing aid are reporting active devices. Going with"
- + " the most recently reported active device: %s");
- return mMostRecentlyReportedActiveDevice;
+ if (bluetoothLeAudio != null) {
+ for (BluetoothDevice device : bluetoothLeAudio.getActiveDevices()) {
+ if (device != null) {
+ leAudioActiveDevice = device;
+ activeDevices++;
+ break;
+ }
}
- return hfpAudioOnDevice;
}
- return hearingAidActiveDevice;
+
+ // Return the active device reported by either HFP, hearing aid or le audio. If more than
+ // one is reporting active devices, go with the most recent one as reported by the receiver.
+ if (activeDevices > 1) {
+ Log.i(this, "More than one profile reporting active devices. Going with the most"
+ + " recently reported active device: %s", mMostRecentlyReportedActiveDevice);
+ return mMostRecentlyReportedActiveDevice;
+ }
+
+ if (leAudioActiveDevice != null) {
+ return leAudioActiveDevice;
+ }
+
+ if (hearingAidActiveDevice != null) {
+ return hearingAidActiveDevice;
+ }
+
+ return hfpAudioOnDevice;
}
/**
@@ -847,10 +895,12 @@
}
@VisibleForTesting
- public void setActiveDeviceCacheForTesting(BluetoothDevice device, boolean isHearingAid) {
- if (isHearingAid) {
+ public void setActiveDeviceCacheForTesting(BluetoothDevice device, int deviceType) {
+ if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) {
+ mLeAudioDeviceCache = device;
+ } else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID) {
mHearingAidActiveDeviceCache = device;
- } else {
+ } else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEADSET) {
mHfpActiveDeviceCache = device;
}
}
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index 8a14cbd..49489f4 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -19,6 +19,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -43,6 +44,10 @@
INTENT_FILTER.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
INTENT_FILTER.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
INTENT_FILTER.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
+ INTENT_FILTER.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
+ INTENT_FILTER.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
+ INTENT_FILTER.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_GROUP_NODE_STATUS_CHANGED);
+ INTENT_FILTER.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_GROUP_STATUS_CHANGED);
}
// If not in a call, BSR won't listen to the Bluetooth stack's HFP on/off messages, since
@@ -59,14 +64,19 @@
case BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED:
handleAudioStateChanged(intent);
break;
+ case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED:
case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED:
case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED:
handleConnectionStateChanged(intent);
break;
+ case BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED:
case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED:
handleActiveDeviceChanged(intent);
break;
+ case BluetoothLeAudio.ACTION_LE_AUDIO_GROUP_NODE_STATUS_CHANGED:
+ handleGroupNodeStatusChanged(intent);
+ break;
}
} finally {
Log.endSession();
@@ -117,29 +127,52 @@
return;
}
- Log.i(LOG_TAG, "Device %s changed state to %d",
+ int deviceType;
+ if (BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
+ deviceType = BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO;
+ } else if (BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
+ deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID;
+ } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
+ deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEADSET;
+ } else {
+ Log.w(LOG_TAG, "handleConnectionStateChanged: %s invalid device type", device);
+ return;
+ }
+
+ Log.i(LOG_TAG, "%s device %s changed state to %d",
+ BluetoothDeviceManager.getDeviceTypeString(deviceType),
device.getAddress(), bluetoothHeadsetState);
- boolean isHearingAid = BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED
- .equals(intent.getAction());
if (bluetoothHeadsetState == BluetoothProfile.STATE_CONNECTED) {
- mBluetoothDeviceManager.onDeviceConnected(device, isHearingAid);
+ mBluetoothDeviceManager.onDeviceConnected(device, deviceType);
} else if (bluetoothHeadsetState == BluetoothProfile.STATE_DISCONNECTED
|| bluetoothHeadsetState == BluetoothProfile.STATE_DISCONNECTING) {
- mBluetoothDeviceManager.onDeviceDisconnected(device, isHearingAid);
+ mBluetoothDeviceManager.onDeviceDisconnected(device, deviceType);
}
}
private void handleActiveDeviceChanged(Intent intent) {
BluetoothDevice device =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- boolean isHearingAid =
- BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED.equals(intent.getAction());
- Log.i(LOG_TAG, "Device %s is now the preferred BT device for %s", device,
- isHearingAid ? "hearing aid" : "HFP");
- mBluetoothRouteManager.onActiveDeviceChanged(device, isHearingAid);
- if (isHearingAid) {
+ int deviceType;
+ if (BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) {
+ deviceType = BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO;
+ } else if (BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) {
+ deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID;
+ } else if (BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) {
+ deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEADSET;
+ } else {
+ Log.w(LOG_TAG, "handleActiveDeviceChanged: %s invalid device type", device);
+ return;
+ }
+
+ Log.i(LOG_TAG, "Device %s is now the preferred BT device for %s", device,
+ BluetoothDeviceManager.getDeviceTypeString(deviceType));
+
+ mBluetoothRouteManager.onActiveDeviceChanged(device, deviceType);
+ if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID ||
+ deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) {
Session session = Log.createSubsession();
SomeArgs args = SomeArgs.obtain();
args.arg1 = session;
@@ -147,12 +180,28 @@
mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args);
} else {
if (!mIsInCall) {
- Log.i(LOG_TAG, "Ignoring hearing aid audio on since we're not in a call");
+ Log.i(LOG_TAG, "Ignoring audio on since we're not in a call");
return;
}
args.arg2 = device.getAddress();
mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args);
- }
+ }
+ }
+ }
+
+ private void handleGroupNodeStatusChanged(Intent intent) {
+ BluetoothDevice device =
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+ int groupId = intent.getIntExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_ID,
+ BluetoothLeAudio.GROUP_ID_INVALID);
+ int groupNodeStatus = intent.getIntExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_NODE_STATUS,
+ -1);
+
+ if (groupNodeStatus == BluetoothLeAudio.GROUP_NODE_ADDED) {
+ mBluetoothDeviceManager.onGroupNodeAdded(device, groupId);
+ } else {
+ mBluetoothDeviceManager.onGroupNodeRemoved(device, groupId);
}
}
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
index db26aaf..fe156dc 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
@@ -20,6 +20,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Intent;
@@ -44,6 +45,7 @@
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -55,6 +57,7 @@
@Mock BluetoothHeadset mBluetoothHeadset;
@Mock BluetoothAdapter mAdapter;
@Mock BluetoothHearingAid mBluetoothHearingAid;
+ @Mock BluetoothLeAudio mBluetoothLeAudio;
BluetoothDeviceManager mBluetoothDeviceManager;
BluetoothProfile.ServiceListener serviceListenerUnderTest;
@@ -64,6 +67,8 @@
private BluetoothDevice device2;
private BluetoothDevice device3;
private BluetoothDevice device4;
+ private BluetoothDevice device5;
+ private BluetoothDevice device6;
@Override
@Before
@@ -75,6 +80,9 @@
device3 = makeBluetoothDevice("00:00:00:00:00:03");
// hearing aid
device4 = makeBluetoothDevice("00:00:00:00:00:04");
+ // le audio
+ device5 = makeBluetoothDevice("00:00:00:00:00:05");
+ device6 = makeBluetoothDevice("00:00:00:00:00:06");
when(mBluetoothHearingAid.getHiSyncId(device2)).thenReturn(100L);
when(mBluetoothHearingAid.getHiSyncId(device4)).thenReturn(100L);
@@ -93,6 +101,7 @@
mBluetoothDeviceManager.setHeadsetServiceForTesting(mBluetoothHeadset);
mBluetoothDeviceManager.setHearingAidServiceForTesting(mBluetoothHearingAid);
+ mBluetoothDeviceManager.setLeAudioServiceForTesting(mBluetoothLeAudio);
}
@Override
@@ -105,10 +114,12 @@
@Test
public void testSingleDeviceConnectAndDisconnect() {
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
assertEquals(1, mBluetoothDeviceManager.getNumConnectedDevices());
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device1, false));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device1,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
assertEquals(0, mBluetoothDeviceManager.getNumConnectedDevices());
}
@@ -119,9 +130,14 @@
mBluetoothDeviceManager.setHearingAidServiceForTesting(null);
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2,
+ BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID));
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
assertEquals(0, mBluetoothDeviceManager.getNumConnectedDevices());
}
@@ -130,16 +146,37 @@
@Test
public void testMultiDeviceConnectAndDisconnect() {
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2,
+ BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device1, false));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3, false));
- assertEquals(2, mBluetoothDeviceManager.getNumConnectedDevices());
+ buildGroupNodeStatusChangedIntent(1, device5, BluetoothLeAudio.GROUP_NODE_ADDED));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device3, false));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device1,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device6,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+ receiverUnderTest.onReceive(mContext,
+ buildGroupNodeStatusChangedIntent(2, device6, BluetoothLeAudio.GROUP_NODE_ADDED));
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device5,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+ assertEquals(3, mBluetoothDeviceManager.getNumConnectedDevices());
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device3,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device6,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
assertEquals(1, mBluetoothDeviceManager.getNumConnectedDevices());
}
@@ -147,11 +184,34 @@
@Test
public void testHearingAidDedup() {
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2,
+ BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device4, true));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device4,
+ BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID));
+ assertEquals(3, mBluetoothDeviceManager.getNumConnectedDevices());
+ assertEquals(2, mBluetoothDeviceManager.getUniqueConnectedDevices().size());
+ }
+
+ @SmallTest
+ @Test
+ public void testLeAudioDedup() {
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+ receiverUnderTest.onReceive(mContext,
+ buildGroupNodeStatusChangedIntent(1, device5, BluetoothLeAudio.GROUP_NODE_ADDED));
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device6,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+ receiverUnderTest.onReceive(mContext,
+ buildGroupNodeStatusChangedIntent(1, device6, BluetoothLeAudio.GROUP_NODE_ADDED));
assertEquals(3, mBluetoothDeviceManager.getNumConnectedDevices());
assertEquals(2, mBluetoothDeviceManager.getUniqueConnectedDevices().size());
}
@@ -160,14 +220,18 @@
@Test
public void testHeadsetServiceDisconnect() {
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3, false));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2,
+ BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID));
serviceListenerUnderTest.onServiceDisconnected(BluetoothProfile.HEADSET);
- verify(mRouteManager).onActiveDeviceChanged(isNull(), eq(false));
+ verify(mRouteManager).onActiveDeviceChanged(isNull(),
+ eq(BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
verify(mRouteManager).onDeviceLost(device1.getAddress());
verify(mRouteManager).onDeviceLost(device3.getAddress());
verify(mRouteManager, never()).onDeviceLost(device2.getAddress());
@@ -179,14 +243,18 @@
@Test
public void testHearingAidServiceDisconnect() {
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3, false));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2,
+ BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID));
serviceListenerUnderTest.onServiceDisconnected(BluetoothProfile.HEARING_AID);
- verify(mRouteManager).onActiveDeviceChanged(isNull(), eq(true));
+ verify(mRouteManager).onActiveDeviceChanged(isNull(),
+ eq(BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID));
verify(mRouteManager).onDeviceLost(device2.getAddress());
verify(mRouteManager, never()).onDeviceLost(device1.getAddress());
verify(mRouteManager, never()).onDeviceLost(device3.getAddress());
@@ -196,16 +264,58 @@
@SmallTest
@Test
+ public void testLeAudioServiceDisconnect() {
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothLeAudio.STATE_CONNECTED, device5,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+ serviceListenerUnderTest.onServiceDisconnected(BluetoothProfile.LE_AUDIO);
+
+ verify(mRouteManager).onActiveDeviceChanged(isNull(),
+ eq(BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+ verify(mRouteManager).onDeviceLost(device5.getAddress());
+ verify(mRouteManager, never()).onDeviceLost(device1.getAddress());
+ verify(mRouteManager, never()).onDeviceLost(device3.getAddress());
+ assertNull(mBluetoothDeviceManager.getLeAudioService());
+ assertEquals(2, mBluetoothDeviceManager.getNumConnectedDevices());
+ }
+
+ @SmallTest
+ @Test
public void testHearingAidChangesIgnoredWhenNotInCall() {
receiverUnderTest.setIsInCall(false);
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2,
+ BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID));
Intent activeDeviceChangedIntent =
new Intent(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
activeDeviceChangedIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device2);
receiverUnderTest.onReceive(mContext, activeDeviceChangedIntent);
- verify(mRouteManager).onActiveDeviceChanged(device2, true);
+ verify(mRouteManager).onActiveDeviceChanged(device2,
+ BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID);
+ verify(mRouteManager, never()).sendMessage(BluetoothRouteManager.BT_AUDIO_IS_ON);
+ }
+
+ @SmallTest
+ @Test
+ public void testLeAudioGroupChangesIgnoredWhenNotInCall() {
+ receiverUnderTest.setIsInCall(false);
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothLeAudio.STATE_CONNECTED, device5,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+ Intent activeDeviceChangedIntent =
+ new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
+ activeDeviceChangedIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device5);
+ receiverUnderTest.onReceive(mContext, activeDeviceChangedIntent);
+
+ verify(mRouteManager).onActiveDeviceChanged(device5,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO);
verify(mRouteManager, never()).sendMessage(BluetoothRouteManager.BT_AUDIO_IS_ON);
}
@@ -213,7 +323,8 @@
@Test
public void testConnectDisconnectAudioHeadset() {
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
mBluetoothDeviceManager.connectAudio(device1.getAddress());
@@ -228,7 +339,10 @@
@Test
public void testConnectDisconnectAudioHearingAid() {
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2,
+ BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID));
+ when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
+ eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
mBluetoothDeviceManager.connectAudio(device2.getAddress());
verify(mAdapter).setActiveDevice(device2, BluetoothAdapter.ACTIVE_DEVICE_ALL);
verify(mBluetoothHeadset, never()).connectAudio();
@@ -243,16 +357,98 @@
verify(mBluetoothHeadset).disconnectAudio();
}
- private Intent buildConnectionActionIntent(int state, BluetoothDevice device,
- boolean isHearingAid) {
- Intent i = new Intent(isHearingAid
- ? BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED
- : BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+ @SmallTest
+ @Test
+ public void testConnectDisconnectAudioLeAudio() {
+ receiverUnderTest.setIsInCall(true);
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+ receiverUnderTest.onReceive(mContext,
+ buildGroupNodeStatusChangedIntent(1, device5, BluetoothLeAudio.GROUP_NODE_ADDED));
+ when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
+ eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
+ mBluetoothDeviceManager.connectAudio(device5.getAddress());
+ verify(mAdapter).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_ALL);
+ verify(mBluetoothHeadset, never()).connectAudio();
+ verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
+ eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
+
+ mBluetoothDeviceManager.disconnectAudio();
+ verify(mAdapter).removeActiveDevice(BluetoothAdapter.ACTIVE_DEVICE_ALL);
+ }
+
+ @SmallTest
+ @Test
+ public void testConnectEarbudLeAudio() {
+ receiverUnderTest.setIsInCall(true);
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+ receiverUnderTest.onReceive(mContext,
+ buildGroupNodeStatusChangedIntent(1, device5, BluetoothLeAudio.GROUP_NODE_ADDED));
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothLeAudio.STATE_CONNECTED, device6,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+ receiverUnderTest.onReceive(mContext,
+ buildGroupNodeStatusChangedIntent(1, device6, BluetoothLeAudio.GROUP_NODE_ADDED));
+ when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
+ eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
+ mBluetoothDeviceManager.connectAudio(device5.getAddress());
+ verify(mAdapter).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_ALL);
+ verify(mBluetoothHeadset, never()).connectAudio();
+ verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
+ eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
+
+ when(mBluetoothLeAudio.getActiveDevices()).thenReturn(Arrays.asList(device5, device6));
+
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device5,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+
+ mBluetoothDeviceManager.connectAudio(device6.getAddress());
+ verify(mAdapter).setActiveDevice(device6, BluetoothAdapter.ACTIVE_DEVICE_ALL);
+ }
+
+ private Intent buildConnectionActionIntent(int state, BluetoothDevice device, int deviceType) {
+ String intentString;
+
+ switch (deviceType) {
+ case BluetoothDeviceManager.DEVICE_TYPE_HEADSET:
+ intentString = BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED;
+ break;
+ case BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID:
+ intentString = BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED;
+ break;
+ case BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO:
+ intentString = BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED;
+ break;
+ default:
+ return null;
+ }
+
+ Intent i = new Intent(intentString);
i.putExtra(BluetoothHeadset.EXTRA_STATE, state);
i.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
return i;
}
+ private Intent buildGroupNodeStatusChangedIntent(int groupId, BluetoothDevice device,
+ int nodeStatus) {
+ Intent i = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_GROUP_NODE_STATUS_CHANGED);
+ i.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ i.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_ID, groupId);
+ i.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_NODE_STATUS, nodeStatus);
+ return i;
+ }
+
+ private Intent buildGroupStatusChangedIntent(int groupId, int groupStatus) {
+ Intent i = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_GROUP_STATUS_CHANGED);
+ i.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_ID, groupId);
+ i.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_STATUS, groupStatus);
+ return i;
+ }
+
private BluetoothDevice makeBluetoothDevice(String address) {
Parcel p1 = Parcel.obtain();
p1.writeString(address);
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
index 28f6966..d6a6d11 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
@@ -21,6 +21,7 @@
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothLeAudio;
import android.content.ContentResolver;
import android.os.Parcel;
import android.telecom.Log;
@@ -65,6 +66,7 @@
@Mock private BluetoothDeviceManager mDeviceManager;
@Mock private BluetoothHeadset mBluetoothHeadset;
@Mock private BluetoothHearingAid mBluetoothHearingAid;
+ @Mock private BluetoothLeAudio mBluetoothLeAudio;
@Mock private Timeouts.Adapter mTimeoutsAdapter;
@Mock private BluetoothRouteManager.BluetoothStateListener mListener;
@@ -85,7 +87,7 @@
public void testConnectHfpRetryWhileNotConnected() {
BluetoothRouteManager sm = setupStateMachine(
BluetoothRouteManager.AUDIO_OFF_STATE_NAME, null);
- setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, null, null, null);
+ setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, null, null, null, null, null);
when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
nullable(ContentResolver.class))).thenReturn(0L);
when(mBluetoothHeadset.connectAudio()).thenReturn(false);
@@ -108,13 +110,17 @@
BluetoothRouteManager sm = setupStateMachine(
BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1);
setupConnectedDevices(new BluetoothDevice[]{DEVICE1},
- new BluetoothDevice[]{HEARING_AID_DEVICE}, DEVICE1, HEARING_AID_DEVICE);
- sm.onActiveDeviceChanged(DEVICE1, false);
- sm.onActiveDeviceChanged(HEARING_AID_DEVICE, true);
+ new BluetoothDevice[]{HEARING_AID_DEVICE}, new BluetoothDevice[]{DEVICE2},
+ DEVICE1, HEARING_AID_DEVICE, DEVICE2);
+ sm.onActiveDeviceChanged(DEVICE1, BluetoothDeviceManager.DEVICE_TYPE_HEADSET);
+ sm.onActiveDeviceChanged(DEVICE2, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO);
+ sm.onActiveDeviceChanged(HEARING_AID_DEVICE,
+ BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID);
executeRoutingAction(sm, BluetoothRouteManager.BT_AUDIO_LOST, DEVICE1.getAddress());
verifyConnectionAttempt(HEARING_AID_DEVICE, 0);
verifyConnectionAttempt(DEVICE1, 0);
+ verifyConnectionAttempt(DEVICE2, 0);
assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
+ ":" + HEARING_AID_DEVICE.getAddress(),
sm.getCurrentState().getName());
@@ -126,7 +132,7 @@
public void testAudioOnDeviceWithScoOffActiveDevice() {
BluetoothRouteManager sm = setupStateMachine(
BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1);
- setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, null, DEVICE1, null);
+ setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, null, null, DEVICE1, null, null);
when(mBluetoothHeadset.getAudioState(DEVICE1))
.thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
executeRoutingAction(sm, BluetoothRouteManager.BT_AUDIO_LOST, DEVICE1.getAddress());
@@ -142,7 +148,8 @@
public void testConnectHfpRetryWhileConnectedToAnotherDevice() {
BluetoothRouteManager sm = setupStateMachine(
BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1);
- setupConnectedDevices(new BluetoothDevice[]{DEVICE1, DEVICE2}, null, null, null);
+ setupConnectedDevices(new BluetoothDevice[]{DEVICE1, DEVICE2}, null, null, null, null,
+ null);
when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
nullable(ContentResolver.class))).thenReturn(0L);
when(mBluetoothHeadset.connectAudio()).thenReturn(false);
@@ -175,16 +182,18 @@
}
private void setupConnectedDevices(BluetoothDevice[] hfpDevices,
- BluetoothDevice[] hearingAidDevices,
- BluetoothDevice hfpActiveDevice, BluetoothDevice hearingAidActiveDevice) {
+ BluetoothDevice[] hearingAidDevices, BluetoothDevice[] leAudioDevices,
+ BluetoothDevice hfpActiveDevice, BluetoothDevice hearingAidActiveDevice,
+ BluetoothDevice leAudioDevice) {
if (hfpDevices == null) hfpDevices = new BluetoothDevice[]{};
if (hearingAidDevices == null) hearingAidDevices = new BluetoothDevice[]{};
+ if (leAudioDevice == null) leAudioDevices = new BluetoothDevice[]{};
when(mDeviceManager.getNumConnectedDevices()).thenReturn(
- hfpDevices.length + hearingAidDevices.length);
- List<BluetoothDevice> allDevices = Stream.concat(
- Arrays.stream(hfpDevices), Arrays.stream(hearingAidDevices))
- .collect(Collectors.toList());
+ hfpDevices.length + hearingAidDevices.length + leAudioDevices.length);
+ List<BluetoothDevice> allDevices = Stream.of(
+ Arrays.stream(hfpDevices), Arrays.stream(hearingAidDevices),
+ Arrays.stream(leAudioDevices)).flatMap(i -> i).collect(Collectors.toList());
when(mDeviceManager.getConnectedDevices()).thenReturn(allDevices);
when(mBluetoothHeadset.getConnectedDevices()).thenReturn(Arrays.asList(hfpDevices));
@@ -197,6 +206,8 @@
.thenReturn(Arrays.asList(hearingAidDevices));
when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.HEARING_AID)))
.thenReturn(Arrays.asList(hearingAidActiveDevice, null));
+ when(mBluetoothLeAudio.getActiveDevices())
+ .thenReturn(Arrays.asList(leAudioDevice, null));
}
static void executeRoutingAction(BluetoothRouteManager brm, int message, String
@@ -222,6 +233,7 @@
when(mDeviceManager.getBluetoothHeadset()).thenReturn(mBluetoothHeadset);
when(mDeviceManager.getBluetoothHearingAid()).thenReturn(mBluetoothHearingAid);
when(mDeviceManager.getBluetoothAdapter()).thenReturn(mBluetoothAdapter);
+ when(mDeviceManager.getLeAudioService()).thenReturn(mBluetoothLeAudio);
when(mBluetoothHeadset.connectAudio()).thenReturn(true);
when(mBluetoothHeadset.setActiveDevice(nullable(BluetoothDevice.class))).thenReturn(true);
when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
index d96b687..ef16eff 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
@@ -21,6 +21,7 @@
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothLeAudio;
import android.content.ContentResolver;
import android.telecom.Log;
import android.test.suitebuilder.annotation.SmallTest;
@@ -43,6 +44,8 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Stream;
+import java.util.stream.Collectors;
import static com.android.server.telecom.tests.BluetoothRouteManagerTest.DEVICE1;
import static com.android.server.telecom.tests.BluetoothRouteManagerTest.DEVICE2;
@@ -80,6 +83,7 @@
// the active device as returned by BluetoothAdapter#getActiveDevices
private BluetoothDevice activeDevice = null;
private List<BluetoothDevice> hearingAidBtDevices = Collections.emptyList();
+ private List<BluetoothDevice> leAudioDevices = Collections.emptyList();
public BluetoothRouteTestParametersBuilder setName(String name) {
this.name = name;
@@ -154,6 +158,12 @@
return this;
}
+ public BluetoothRouteTestParametersBuilder setLeAudioDevices(
+ List<BluetoothDevice> leAudioDevices) {
+ this.leAudioDevices = leAudioDevices;
+ return this;
+ }
+
public BluetoothRouteTestParameters build() {
return new BluetoothRouteTestParameters(name,
initialBluetoothState,
@@ -167,7 +177,8 @@
messageDevice,
audioOnDevice,
activeDevice,
- hearingAidBtDevices);
+ hearingAidBtDevices,
+ leAudioDevices);
}
}
@@ -187,6 +198,7 @@
// the active device as returned by BluetoothAdapter#getActiveDevices
private BluetoothDevice activeDevice = null;
private List<BluetoothDevice> hearingAidBtDevices;
+ private List<BluetoothDevice> leAudioDevices;
public BluetoothRouteTestParameters(String name, String initialBluetoothState,
BluetoothDevice initialDevice, int messageType, ListenerUpdate[]
@@ -194,7 +206,7 @@
expectedConnectionDevice, String expectedFinalStateName,
BluetoothDevice[] connectedDevices, BluetoothDevice messageDevice,
BluetoothDevice audioOnDevice, BluetoothDevice activeDevice,
- List<BluetoothDevice> hearingAidBtDevices) {
+ List<BluetoothDevice> hearingAidBtDevices, List<BluetoothDevice> leAudioDevices) {
this.name = name;
this.initialBluetoothState = initialBluetoothState;
this.initialDevice = initialDevice;
@@ -208,6 +220,7 @@
this.audioOnDevice = audioOnDevice;
this.activeDevice = activeDevice;
this.hearingAidBtDevices = hearingAidBtDevices;
+ this.leAudioDevices = leAudioDevices;
}
@Override
@@ -225,6 +238,7 @@
", connectedDevices=" + Arrays.toString(connectedDevices) +
", activeDevice='" + activeDevice + '\'' +
", hearingAidBtDevices ='" + hearingAidBtDevices + '\'' +
+ ", leAudioDevices ='" + leAudioDevices + '\'' +
'}';
}
}
@@ -240,6 +254,7 @@
@Mock private BluetoothAdapter mBluetoothAdapter;
@Mock private BluetoothHeadset mBluetoothHeadset;
@Mock private BluetoothHearingAid mBluetoothHearingAid;
+ @Mock private BluetoothLeAudio mBluetoothLeAudio;
@Mock private Timeouts.Adapter mTimeoutsAdapter;
@Mock private BluetoothRouteManager.BluetoothStateListener mListener;
@@ -266,10 +281,16 @@
BluetoothRouteManager sm = setupStateMachine(
mParams.initialBluetoothState, mParams.initialDevice);
+ int deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEADSET;
+ if (mParams.hearingAidBtDevices.contains(mParams.messageDevice)) {
+ deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID;
+ } else if (mParams.hearingAidBtDevices.contains(mParams.messageDevice)) {
+ deviceType = BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO;
+ }
+
setupConnectedDevices(mParams.connectedDevices,
mParams.audioOnDevice, mParams.activeDevice);
- sm.setActiveDeviceCacheForTesting(mParams.activeDevice,
- mParams.hearingAidBtDevices.contains(mParams.messageDevice));
+ sm.setActiveDeviceCacheForTesting(mParams.activeDevice, deviceType);
if (mParams.initialDevice != null) {
doAnswer(invocation -> {
SomeArgs args = SomeArgs.obtain();
@@ -285,14 +306,16 @@
// Go through the utility methods for these two messages
if (mParams.messageType == BluetoothRouteManager.NEW_DEVICE_CONNECTED) {
sm.onDeviceAdded(mParams.messageDevice.getAddress());
- sm.onActiveDeviceChanged(mParams.messageDevice,
- mParams.hearingAidBtDevices.contains(mParams.messageDevice));
+ sm.onActiveDeviceChanged(mParams.messageDevice, deviceType);
} else if (mParams.messageType == BluetoothRouteManager.LOST_DEVICE) {
- sm.onActiveDeviceChanged(null,
- mParams.hearingAidBtDevices.contains(mParams.messageDevice));
+ sm.onActiveDeviceChanged(null, deviceType);
if (mParams.hearingAidBtDevices.contains(mParams.messageDevice)) {
when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.HEARING_AID)))
.thenReturn(Arrays.asList(null, null));
+ when(mBluetoothLeAudio.getActiveDevices())
+ .thenReturn(mParams.leAudioDevices.stream()
+ .filter(device -> device != mParams.messageDevice)
+ .collect(Collectors.toList()));
} else {
when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.HEADSET)))
.thenReturn(Arrays.asList((BluetoothDevice) null));
@@ -368,6 +391,7 @@
resetMocks();
when(mDeviceManager.getBluetoothHeadset()).thenReturn(mBluetoothHeadset);
when(mDeviceManager.getBluetoothHearingAid()).thenReturn(mBluetoothHearingAid);
+ when(mDeviceManager.getLeAudioService()).thenReturn(mBluetoothLeAudio);
when(mDeviceManager.connectAudio(nullable(String.class))).thenReturn(true);
when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
nullable(ContentResolver.class))).thenReturn(100000L);
@@ -660,6 +684,36 @@
+ ":" + DEVICE2)
.build());
+ result.add(new BluetoothRouteTestParametersBuilder()
+ .setName("le audio device disconnects with hearing aid present")
+ .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX)
+ .setInitialDevice(DEVICE2)
+ .setConnectedDevices(DEVICE2, DEVICE3)
+ .setLeAudioDevices(Collections.singletonList(DEVICE2))
+ .setHearingAidBtDevices(Collections.singletonList(DEVICE3))
+ .setMessageType(BluetoothRouteManager.LOST_DEVICE)
+ .setMessageDevice(DEVICE2)
+ .setExpectedListenerUpdates(ListenerUpdate.AUDIO_DISCONNECTED,
+ ListenerUpdate.DEVICE_LIST_CHANGED)
+ .setExpectedBluetoothInteraction(NONE)
+ .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_OFF_STATE_NAME)
+ .build());
+
+ result.add(new BluetoothRouteTestParametersBuilder()
+ .setName("le audio device disconnects with another one connected")
+ .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX)
+ .setInitialDevice(DEVICE1)
+ .setConnectedDevices(DEVICE1, DEVICE2, DEVICE3)
+ .setHearingAidBtDevices(Collections.singletonList(DEVICE3))
+ .setLeAudioDevices(Arrays.asList(DEVICE1, DEVICE2))
+ .setMessageType(BluetoothRouteManager.LOST_DEVICE)
+ .setMessageDevice(DEVICE1)
+ .setExpectedListenerUpdates(ListenerUpdate.AUDIO_DISCONNECTED,
+ ListenerUpdate.DEVICE_LIST_CHANGED)
+ .setExpectedBluetoothInteraction(NONE)
+ .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_OFF_STATE_NAME)
+ .build());
+
return result;
}
}