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:
@@ -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() {
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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. "
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user