Handle multiple active bluetooth devices

When dealing with a pair of hearing aid devices are active,
a single "selected route" is not sufficient.

Instead, this CL introduces activeRoutes which can hold multiple active
bluetooth route and deal with them.

Bug: 152585170
Test: cts tested & manually w/ A2dp BT devices
Change-Id: I2c2b8967d9f221db73f9d3b9332de7c76d07a0f9
This commit is contained in:
Kyunglyul Hyun
2020-06-21 21:14:43 +09:00
parent f83fb7e2ae
commit e1b8006e2b
2 changed files with 95 additions and 35 deletions

View File

@@ -344,6 +344,8 @@ public final class MediaRouter2Manager {
Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
Objects.requireNonNull(route, "route must not be null");
Log.v(TAG, "Transferring routing session. session= " + sessionInfo + ", route=" + route);
synchronized (mRoutesLock) {
if (!mRoutes.containsKey(route.getId())) {
Log.w(TAG, "transfer: Ignoring an unknown route id=" + route.getId());

View File

@@ -17,6 +17,8 @@
package com.android.server.media;
import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_AUDIO;
import static android.bluetooth.BluetoothAdapter.STATE_CONNECTED;
import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTED;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -33,6 +35,7 @@ import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.MediaRoute2Info;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
@@ -48,6 +51,8 @@ import java.util.Objects;
class BluetoothRouteProvider {
private static final String TAG = "BTRouteProvider";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_";
private static BluetoothRouteProvider sInstance;
@@ -55,7 +60,7 @@ class BluetoothRouteProvider {
// Maps hardware address to BluetoothRouteInfo
final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>();
@SuppressWarnings("WeakerAccess") /* synthetic access */
BluetoothRouteInfo mSelectedRoute = null;
final List<BluetoothRouteInfo> mActiveRoutes = new ArrayList<>();
@SuppressWarnings("WeakerAccess") /* synthetic access */
BluetoothA2dp mA2dpProfile;
@SuppressWarnings("WeakerAccess") /* synthetic access */
@@ -104,7 +109,7 @@ class BluetoothRouteProvider {
// Bluetooth on/off broadcasts
addEventReceiver(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedReceiver());
DeviceStateChangedRecevier deviceStateChangedReceiver = new DeviceStateChangedRecevier();
DeviceStateChangedReceiver deviceStateChangedReceiver = new DeviceStateChangedReceiver();
addEventReceiver(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, deviceStateChangedReceiver);
addEventReceiver(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, deviceStateChangedReceiver);
addEventReceiver(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,
@@ -168,14 +173,16 @@ class BluetoothRouteProvider {
@Nullable
MediaRoute2Info getSelectedRoute() {
return (mSelectedRoute == null) ? null : mSelectedRoute.route;
// For now, active routes can be multiple only when a pair of hearing aid devices is active.
// Let the first active device represent them.
return (mActiveRoutes.isEmpty() ? null : mActiveRoutes.get(0).route);
}
@NonNull
List<MediaRoute2Info> getTransferableRoutes() {
List<MediaRoute2Info> routes = getAllBluetoothRoutes();
if (mSelectedRoute != null) {
routes.remove(mSelectedRoute.route);
for (BluetoothRouteInfo btRoute : mActiveRoutes) {
routes.remove(btRoute.route);
}
return routes;
}
@@ -185,8 +192,14 @@ class BluetoothRouteProvider {
List<MediaRoute2Info> routes = new ArrayList<>();
List<String> routeIds = new ArrayList<>();
MediaRoute2Info selectedRoute = getSelectedRoute();
if (selectedRoute != null) {
routes.add(selectedRoute);
routeIds.add(selectedRoute.getId());
}
for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
// A pair of hearing aid devices or the same hardware address
// A pair of hearing aid devices or having the same hardware address
if (routeIds.contains(btRoute.route.getId())) {
continue;
}
@@ -225,13 +238,20 @@ class BluetoothRouteProvider {
return false;
}
mVolumeMap.put(routeType, volume);
if (mSelectedRoute == null || mSelectedRoute.route.getType() != routeType) {
return true;
boolean shouldNotify = false;
for (BluetoothRouteInfo btRoute : mActiveRoutes) {
if (btRoute.route.getType() != routeType) {
continue;
}
btRoute.route = new MediaRoute2Info.Builder(btRoute.route)
.setVolume(volume)
.build();
shouldNotify = true;
}
if (shouldNotify) {
notifyBluetoothRoutesUpdated();
}
mSelectedRoute.route = new MediaRoute2Info.Builder(mSelectedRoute.route)
.setVolume(volume)
.build();
notifyBluetoothRoutesUpdated();
return true;
}
@@ -297,6 +317,53 @@ class BluetoothRouteProvider {
btRoute.route = builder.build();
}
private void clearActiveRoutes() {
if (DEBUG) {
Log.d(TAG, "Clearing active routes");
}
for (BluetoothRouteInfo btRoute : mActiveRoutes) {
setRouteConnectionState(btRoute, STATE_DISCONNECTED);
}
mActiveRoutes.clear();
}
private void addActiveRoute(BluetoothRouteInfo btRoute) {
if (DEBUG) {
Log.d(TAG, "Adding active route: " + btRoute.route);
}
if (btRoute == null || mActiveRoutes.contains(btRoute)) {
return;
}
setRouteConnectionState(btRoute, STATE_CONNECTED);
mActiveRoutes.add(btRoute);
}
private void removeActiveRoute(BluetoothRouteInfo btRoute) {
if (DEBUG) {
Log.d(TAG, "Removing active route: " + btRoute.route);
}
if (mActiveRoutes.remove(btRoute)) {
setRouteConnectionState(btRoute, STATE_DISCONNECTED);
}
}
private void findAndSetActiveHearingAidDevices() {
if (DEBUG) {
Log.d(TAG, "Setting active hearing aid devices");
}
BluetoothHearingAid hearingAidProfile = mHearingAidProfile;
if (hearingAidProfile == null) {
return;
}
List<BluetoothDevice> activeDevices = hearingAidProfile.getActiveDevices();
for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
if (activeDevices.contains(btRoute.btDevice)) {
addActiveRoute(btRoute);
}
}
}
interface BluetoothRoutesUpdatedListener {
void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes);
}
@@ -333,7 +400,6 @@ class BluetoothRouteProvider {
default:
return;
}
//TODO(b/157708273): Handle two active devices in the binaural case.
for (BluetoothDevice device : proxy.getConnectedDevices()) {
BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
if (btRoute == null) {
@@ -341,9 +407,7 @@ class BluetoothRouteProvider {
mBluetoothRoutes.put(device.getAddress(), btRoute);
}
if (activeDevices.contains(device)) {
mSelectedRoute = btRoute;
setRouteConnectionState(mSelectedRoute,
MediaRoute2Info.CONNECTION_STATE_CONNECTED);
addActiveRoute(btRoute);
}
}
notifyBluetoothRoutesUpdated();
@@ -395,26 +459,23 @@ class BluetoothRouteProvider {
}
}
private class DeviceStateChangedRecevier implements BluetoothEventReceiver {
private class DeviceStateChangedReceiver implements BluetoothEventReceiver {
@Override
public void onReceive(Context context, Intent intent, BluetoothDevice device) {
switch (intent.getAction()) {
case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
if (mSelectedRoute == null
|| !mSelectedRoute.btDevice.equals(device)) {
if (mSelectedRoute != null) {
setRouteConnectionState(mSelectedRoute,
MediaRoute2Info.CONNECTION_STATE_DISCONNECTED);
}
mSelectedRoute = (device == null) ? null
: mBluetoothRoutes.get(device.getAddress());
if (mSelectedRoute != null) {
setRouteConnectionState(mSelectedRoute,
MediaRoute2Info.CONNECTION_STATE_CONNECTED);
}
notifyBluetoothRoutesUpdated();
clearActiveRoutes();
if (device != null) {
addActiveRoute(mBluetoothRoutes.get(device.getAddress()));
}
notifyBluetoothRoutesUpdated();
break;
case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
clearActiveDevices();
if (device != null) {
findAndSetActiveHearingAidDevices();
}
notifyBluetoothRoutesUpdated();
break;
case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
handleConnectionStateChanged(BluetoothProfile.A2DP, intent, device);
@@ -444,10 +505,7 @@ class BluetoothRouteProvider {
if (btRoute != null) {
btRoute.connectedProfiles.delete(profile);
if (btRoute.connectedProfiles.size() == 0) {
mBluetoothRoutes.remove(device.getAddress());
if (mSelectedRoute != null && mSelectedRoute.btDevice.equals(device)) {
mSelectedRoute = null;
}
removeActiveRoute(mBluetoothRoutes.remove(device.getAddress()));
notifyBluetoothRoutesUpdated();
}
}