MediaRouter: enable transferring from/to BT devices.

The main objective of this CL is to enable selecting BT device.
For that it updates MRM and SystemMediaRoute2Provider such that
  - MRM.getRoutingControllers() returns a list including the system session
  - SystemMediaRoute2Provider marks routes as "transferable"
  - SystemMediaRoute2Provider sets provider id correctly
  - SystemMediaRoute2Provider handles transferToRoute()

Bug: 147979868
Bug: 147122575

Test: atest mediaroutertest
&& manually selecting phone / bt devices from output switcher

Change-Id: I2e2032fd6677f79b9f864c313c40846daa87f113
This commit is contained in:
Kyunglyul Hyun
2020-01-21 16:30:28 +09:00
parent 0e4d731bbc
commit 581fc98d65
5 changed files with 119 additions and 58 deletions

View File

@@ -178,8 +178,9 @@ public class MediaRouter2Manager {
/**
* Gets routing controllers of an application with the given package name.
* If the application isn't running or it doesn't use {@link MediaRouter2}, an empty list
* will be returned.
* The first element of the returned list is the system routing controller.
*
* @see MediaRouter2#getSystemController()
*/
@NonNull
public List<RoutingController> getRoutingControllers(@NonNull String packageName) {
@@ -188,7 +189,8 @@ public class MediaRouter2Manager {
List<RoutingController> controllers = new ArrayList<>();
for (RoutingSessionInfo sessionInfo : getActiveSessions()) {
if (TextUtils.equals(sessionInfo.getClientPackageName(), packageName)) {
if (sessionInfo.isSystemSession()
|| TextUtils.equals(sessionInfo.getClientPackageName(), packageName)) {
controllers.add(new RoutingController(sessionInfo));
}
}
@@ -196,8 +198,11 @@ public class MediaRouter2Manager {
}
/**
* Gets the list of all active routing sessions. It doesn't include default routing sessions
* of applications.
* Gets the list of all active routing sessions.
* The first element of the list is the system routing session containing
* phone speakers, wired headset, Bluetooth devices.
* The system routing session is shared by apps such that controlling it will affect
* all apps.
*/
@NonNull
public List<RoutingSessionInfo> getActiveSessions() {

View File

@@ -246,7 +246,7 @@ public class MediaRouterManagerTest {
}
});
assertEquals(0, mManager.getRoutingControllers(mPackageName).size());
assertEquals(1, mManager.getRoutingControllers(mPackageName).size());
mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1));
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
@@ -254,14 +254,14 @@ public class MediaRouterManagerTest {
List<MediaRouter2Manager.RoutingController> controllers =
mManager.getRoutingControllers(mPackageName);
assertEquals(1, controllers.size());
assertEquals(2, controllers.size());
MediaRouter2Manager.RoutingController routingController = controllers.get(0);
MediaRouter2Manager.RoutingController routingController = controllers.get(1);
awaitOnRouteChangedManager(
() -> routingController.release(),
ROUTE_ID1,
route -> TextUtils.equals(route.getClientPackageName(), null));
assertEquals(0, mManager.getRoutingControllers(mPackageName).size());
assertEquals(1, mManager.getRoutingControllers(mPackageName).size());
}
/**
@@ -290,8 +290,8 @@ public class MediaRouterManagerTest {
List<MediaRouter2Manager.RoutingController> controllers =
mManager.getRoutingControllers(mPackageName);
assertEquals(1, controllers.size());
MediaRouter2Manager.RoutingController routingController = controllers.get(0);
assertEquals(2, controllers.size());
MediaRouter2Manager.RoutingController routingController = controllers.get(1);
awaitOnRouteChangedManager(
() -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)),

View File

@@ -29,12 +29,13 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.media.MediaRoute2Info;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
import com.android.internal.R;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -59,7 +60,6 @@ class BluetoothRouteProvider {
private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver();
private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();
// TODO: The mActiveDevice should be set when BluetoothRouteProvider is created.
private BluetoothDevice mActiveDevice = null;
static synchronized BluetoothRouteProvider getInstance(@NonNull Context context,
@@ -104,6 +104,43 @@ class BluetoothRouteProvider {
mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, null);
}
/**
* Clears the active device for all known profiles.
*/
public void clearActiveDevices() {
BluetoothA2dp a2dpProfile = mA2dpProfile;
BluetoothHearingAid hearingAidProfile = mHearingAidProfile;
if (a2dpProfile != null) {
a2dpProfile.setActiveDevice(null);
}
if (hearingAidProfile != null) {
hearingAidProfile.setActiveDevice(null);
}
}
/**
* Sets the active device.
* @param deviceId the id of the Bluetooth device
*/
public void setActiveDevice(@NonNull String deviceId) {
BluetoothRouteInfo btRouteInfo = mBluetoothRoutes.get(deviceId);
if (btRouteInfo == null) {
Slog.w(TAG, "setActiveDevice: unknown device id=" + deviceId);
return;
}
BluetoothA2dp a2dpProfile = mA2dpProfile;
BluetoothHearingAid hearingAidProfile = mHearingAidProfile;
if (a2dpProfile != null
&& btRouteInfo.connectedProfiles.get(BluetoothProfile.A2DP, false)) {
a2dpProfile.setActiveDevice(btRouteInfo.btDevice);
}
if (hearingAidProfile != null
&& btRouteInfo.connectedProfiles.get(BluetoothProfile.HEARING_AID, false)) {
hearingAidProfile.setActiveDevice(btRouteInfo.btDevice);
}
}
private void addEventReceiver(String action, BluetoothEventReceiver eventReceiver) {
mEventReceiverMap.put(action, eventReceiver);
mIntentFilter.addAction(action);
@@ -157,12 +194,12 @@ class BluetoothRouteProvider {
private void setRouteConnectionStateForDevice(BluetoothDevice device,
@MediaRoute2Info.ConnectionState int state) {
if (device == null) {
Log.w(TAG, "setRouteConnectionStateForDevice: device shouldn't be null");
Slog.w(TAG, "setRouteConnectionStateForDevice: device shouldn't be null");
return;
}
BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
if (btRoute == null) {
Log.w(TAG, "setRouteConnectionStateForDevice: route shouldn't be null");
Slog.w(TAG, "setRouteConnectionStateForDevice: route shouldn't be null");
return;
}
if (btRoute.route.getConnectionState() != state) {
@@ -184,24 +221,36 @@ class BluetoothRouteProvider {
// These callbacks run on the main thread.
private final class BluetoothProfileListener implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
List<BluetoothDevice> activeDevices;
switch (profile) {
case BluetoothProfile.A2DP:
mA2dpProfile = (BluetoothA2dp) proxy;
// It may contain null.
activeDevices = Collections.singletonList(mA2dpProfile.getActiveDevice());
break;
case BluetoothProfile.HEARING_AID:
mHearingAidProfile = (BluetoothHearingAid) proxy;
activeDevices = mHearingAidProfile.getActiveDevices();
break;
default:
return;
}
//TODO: Check a pair of HAP devices whether there exist two or more active devices.
for (BluetoothDevice device : proxy.getConnectedDevices()) {
BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
if (btRoute == null) {
btRoute = createBluetoothRoute(device);
mBluetoothRoutes.put(device.getAddress(), btRoute);
}
if (activeDevices.contains(device)) {
mActiveDevice = device;
setRouteConnectionStateForDevice(device,
MediaRoute2Info.CONNECTION_STATE_CONNECTED);
}
btRoute.connectedProfiles.put(profile, true);
}
notifyBluetoothRoutesUpdated();
}
public void onServiceDisconnected(int profile) {

View File

@@ -760,17 +760,12 @@ class MediaRouter2ServiceImpl {
Slog.w(TAG, "selectClientRouteLocked: Ignoring unknown manager.");
return;
}
//TODO: we shouldn't ignore selecting request for unknown clients. (RCN?)
Client2Record clientRecord = managerRecord.mUserRecord.mHandler
.findClientforSessionLocked(sessionId);
if (clientRecord == null) {
Slog.w(TAG, "selectClientRouteLocked: Ignoring unknown session.");
return;
}
clientRecord.mUserRecord.mHandler.sendMessage(
managerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::selectRouteOnHandler,
clientRecord.mUserRecord.mHandler,
managerRecord.mUserRecord.mHandler,
clientRecord, sessionId, route));
}
@@ -783,17 +778,12 @@ class MediaRouter2ServiceImpl {
Slog.w(TAG, "deselectClientRouteLocked: Ignoring unknown manager.");
return;
}
//TODO: we shouldn't ignore selecting request for unknown clients. (RCN?)
Client2Record clientRecord = managerRecord.mUserRecord.mHandler
.findClientforSessionLocked(sessionId);
if (clientRecord == null) {
Slog.w(TAG, "deslectClientRouteLocked: Ignoring unknown session.");
return;
}
clientRecord.mUserRecord.mHandler.sendMessage(
managerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::deselectRouteOnHandler,
clientRecord.mUserRecord.mHandler,
managerRecord.mUserRecord.mHandler,
clientRecord, sessionId, route));
}
@@ -806,17 +796,12 @@ class MediaRouter2ServiceImpl {
Slog.w(TAG, "transferClientRouteLocked: Ignoring unknown manager.");
return;
}
//TODO: we shouldn't ignore selecting request for unknown clients. (RCN?)
Client2Record clientRecord = managerRecord.mUserRecord.mHandler
.findClientforSessionLocked(sessionId);
if (clientRecord == null) {
Slog.w(TAG, "transferClientRouteLocked: Ignoring unknown session.");
return;
}
clientRecord.mUserRecord.mHandler.sendMessage(
managerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::transferToRouteOnHandler,
clientRecord.mUserRecord.mHandler,
managerRecord.mUserRecord.mHandler,
clientRecord, sessionId, route));
}
@@ -1166,7 +1151,7 @@ class MediaRouter2ServiceImpl {
requestId, sessionHints);
}
private void selectRouteOnHandler(@NonNull Client2Record clientRecord,
private void selectRouteOnHandler(@Nullable Client2Record clientRecord,
String uniqueSessionId, MediaRoute2Info route) {
if (!checkArgumentsForSessionControl(clientRecord, uniqueSessionId, route,
"selecting")) {
@@ -1182,7 +1167,7 @@ class MediaRouter2ServiceImpl {
provider.selectRoute(getOriginalId(uniqueSessionId), route.getOriginalId());
}
private void deselectRouteOnHandler(@NonNull Client2Record clientRecord,
private void deselectRouteOnHandler(@Nullable Client2Record clientRecord,
String uniqueSessionId, MediaRoute2Info route) {
if (!checkArgumentsForSessionControl(clientRecord, uniqueSessionId, route,
"deselecting")) {
@@ -1198,7 +1183,7 @@ class MediaRouter2ServiceImpl {
provider.deselectRoute(getOriginalId(uniqueSessionId), route.getOriginalId());
}
private void transferToRouteOnHandler(@NonNull Client2Record clientRecord,
private void transferToRouteOnHandler(Client2Record clientRecord,
String uniqueSessionId, MediaRoute2Info route) {
if (!checkArgumentsForSessionControl(clientRecord, uniqueSessionId, route,
"transferring to")) {
@@ -1215,7 +1200,7 @@ class MediaRouter2ServiceImpl {
route.getOriginalId());
}
private boolean checkArgumentsForSessionControl(@NonNull Client2Record clientRecord,
private boolean checkArgumentsForSessionControl(@Nullable Client2Record clientRecord,
String uniqueSessionId, MediaRoute2Info route, @NonNull String description) {
if (route == null) {
Slog.w(TAG, "Ignoring " + description + " null route");
@@ -1236,6 +1221,17 @@ class MediaRouter2ServiceImpl {
return false;
}
// Bypass checking client if it's the system session (clientRecord should be null)
if (TextUtils.equals(getProviderId(uniqueSessionId), mSystemProvider.getUniqueId())) {
return true;
}
//TODO: Handle RCN case.
if (clientRecord == null) {
Slog.w(TAG, "Ignoring " + description + " route from unknown client.");
return false;
}
Client2Record matchingRecord = mSessionToClientMap.get(uniqueSessionId);
if (matchingRecord != clientRecord) {
Slog.w(TAG, "Ignoring " + description + " route from non-matching client. "

View File

@@ -64,7 +64,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
SystemMediaRoute2Provider.class.getPackageName$(),
SystemMediaRoute2Provider.class.getName());
//TODO: Clean up these when audio manager support multiple bt devices
MediaRoute2Info mDefaultRoute;
@NonNull List<MediaRoute2Info> mBluetoothRoutes = Collections.EMPTY_LIST;
final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
@@ -91,6 +90,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
mAudioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE));
initializeDefaultRoute();
mBtRouteProvider = BluetoothRouteProvider.getInstance(context, (routes) -> {
mBluetoothRoutes = routes;
publishRoutes();
@@ -103,7 +103,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
notifySessionInfoUpdated();
}
});
initializeRoutes();
initializeSessionInfo();
}
@Override
@@ -119,17 +119,21 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
@Override
public void selectRoute(String sessionId, String routeId) {
//TODO: implement method
// Do nothing since we don't support multiple BT yet.
}
@Override
public void deselectRoute(String sessionId, String routeId) {
//TODO: implement method
// Do nothing since we don't support multiple BT yet.
}
@Override
public void transferToRoute(String sessionId, String routeId) {
//TODO: implement method
if (TextUtils.equals(routeId, mDefaultRoute.getId())) {
mBtRouteProvider.clearActiveDevices();
} else {
mBtRouteProvider.setActiveDevice(routeId);
}
}
//TODO: implement method
@@ -147,8 +151,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
public void requestUpdateVolume(String routeId, int delta) {
}
void initializeRoutes() {
//TODO: adds necessary info
private void initializeDefaultRoute() {
mDefaultRoute = new MediaRoute2Info.Builder(
DEFAULT_ROUTE_ID,
mContext.getResources().getText(R.string.default_audio_route_name).toString())
@@ -172,7 +175,9 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
// route yet.
updateAudioRoutes(newAudioRoutes);
}
}
private void initializeSessionInfo() {
mBluetoothRoutes = mBtRouteProvider.getBluetoothRoutes();
MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
@@ -183,11 +188,15 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
setProviderState(builder.build());
mHandler.post(() -> notifyProviderState());
// Note: No lock needed when initializing.
updateSessionInfosIfNeededLocked();
//TODO: clean up this
// This is required because it is not instantiated in the main thread and
// BluetoothRoutesUpdatedListener can be called before this function
synchronized (mLock) {
updateSessionInfosIfNeededLocked();
}
}
void updateAudioRoutes(AudioRoutesInfo newRoutes) {
private void updateAudioRoutes(AudioRoutesInfo newRoutes) {
int name = R.string.default_audio_route_name;
mCurAudioRoutesInfo.mainType = newRoutes.mainType;
if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0
@@ -226,15 +235,22 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
.setSystemSession(true);
String activeBtDeviceAddress = mBtRouteProvider.getActiveDeviceAddress();
RoutingSessionInfo newSessionInfo;
if (!TextUtils.isEmpty(activeBtDeviceAddress)) {
// Bluetooth route. Set the route ID with the device's address.
newSessionInfo = builder.addSelectedRoute(activeBtDeviceAddress).build();
builder.addSelectedRoute(activeBtDeviceAddress);
builder.addTransferrableRoute(mDefaultRoute.getId());
} else {
// Default device
newSessionInfo = builder.addSelectedRoute(mDefaultRoute.getId()).build();
builder.addSelectedRoute(mDefaultRoute.getId());
}
for (MediaRoute2Info route : mBluetoothRoutes) {
if (!TextUtils.equals(activeBtDeviceAddress, route.getId())) {
builder.addTransferrableRoute(route.getId());
}
}
RoutingSessionInfo newSessionInfo = builder.setProviderId(mUniqueId).build();
if (Objects.equals(oldSessionInfo, newSessionInfo)) {
return false;
} else {
@@ -244,11 +260,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
}
}
/**
* The first route should be the currently selected system route.
* For example, if there are two system routes (BT and device speaker),
* BT will be the first route in the list.
*/
void publishRoutes() {
MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
builder.addRoute(mDefaultRoute);