diff options
| author | 2020-01-21 16:30:28 +0900 | |
|---|---|---|
| committer | 2020-01-22 20:46:22 +0900 | |
| commit | 581fc98d65bc8426d08359c5f03c29df424572d1 (patch) | |
| tree | 1fcdf80759b28911058cec44f24ff6548fbdfc19 | |
| parent | 0e4d731bbcb9b6b6a4310c83aea9a4fab0e1c35f (diff) | |
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
5 files changed, 119 insertions, 58 deletions
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 5cb32d6ab550..61e2f77b7d2e 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -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() { diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index 7726e90674f2..4a2044af0431 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -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)), diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java index 837c489c0cac..8179c32ae2df 100644 --- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java +++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java @@ -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) { diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index f9169ee69d41..9594659f23d0 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -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. " diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 6f6d8a1e8df4..558eb8d05783 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -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); |