diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java
new file mode 100644
index 0000000000000..08691d035f293
--- /dev/null
+++ b/services/core/java/com/android/server/media/BluetoothRouteController.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.content.Context;
+import android.media.MediaRoute2Info;
+import android.os.UserHandle;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Provides control over bluetooth routes.
+ */
+/* package */ interface BluetoothRouteController {
+
+ /**
+ * Returns a new instance of {@link LegacyBluetoothRouteController}.
+ *
+ *
It may return {@link NoOpBluetoothRouteController} if Bluetooth is not supported on this
+ * hardware platform.
+ */
+ @NonNull
+ static BluetoothRouteController createInstance(@NonNull Context context,
+ @NonNull BluetoothRouteController.BluetoothRoutesUpdatedListener listener) {
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(listener);
+
+ BluetoothManager bluetoothManager = (BluetoothManager)
+ context.getSystemService(Context.BLUETOOTH_SERVICE);
+ BluetoothAdapter btAdapter = bluetoothManager.getAdapter();
+
+ if (btAdapter == null) {
+ return new NoOpBluetoothRouteController();
+ }
+
+ return new LegacyBluetoothRouteController(context, btAdapter, listener);
+ }
+
+ /**
+ * Makes the controller to listen to events from Bluetooth stack.
+ *
+ * @param userHandle is needed to subscribe for broadcasts on user's behalf.
+ */
+ void start(@NonNull UserHandle userHandle);
+
+ /**
+ * Stops the controller from listening to any Bluetooth events.
+ */
+ void stop();
+
+ /**
+ * Transfers Bluetooth output to the given route.
+ *
+ *
If the route is {@code null} then active route will be deactivated.
+ *
+ * @param routeId to switch to or {@code null} to unset the active device.
+ */
+ void transferTo(@Nullable String routeId);
+
+ /**
+ * Returns currently selected Bluetooth route.
+ *
+ * @return the selected route or {@code null} if there are no active routes.
+ */
+ @Nullable
+ MediaRoute2Info getSelectedRoute();
+
+ /**
+ * Returns transferable routes.
+ *
+ *
A route is considered to be transferable if the bluetooth device is connected but not
+ * considered as selected.
+ *
+ * @return list of transferable routes or an empty list.
+ */
+ @NonNull
+ List getTransferableRoutes();
+
+ /**
+ * Provides all connected Bluetooth routes.
+ *
+ * @return list of Bluetooth routes or an empty list.
+ */
+ @NonNull
+ List getAllBluetoothRoutes();
+
+ /**
+ * Updates the volume for all Bluetooth devices for the given profile.
+ *
+ * @param devices specifies the profile, may be, {@link android.bluetooth.BluetoothA2dp}, {@link
+ * android.bluetooth.BluetoothLeAudio}, or {@link android.bluetooth.BluetoothHearingAid}
+ * @param volume the specific volume value for the given devices or 0 if unknown.
+ * @return {@code true} if updated successfully and {@code false} otherwise.
+ */
+ boolean updateVolumeForDevices(int devices, int volume);
+
+ /**
+ * Interface for receiving events about Bluetooth routes changes.
+ */
+ interface BluetoothRoutesUpdatedListener {
+
+ /**
+ * Called when Bluetooth routes have changed.
+ *
+ * @param routes updated Bluetooth routes list.
+ */
+ void onBluetoothRoutesUpdated(@NonNull List routes);
+ }
+
+ /**
+ * No-op implementation of {@link BluetoothRouteController}.
+ *
+ * Useful if the device does not support Bluetooth.
+ */
+ class NoOpBluetoothRouteController implements BluetoothRouteController {
+
+ @Override
+ public void start(UserHandle userHandle) {
+ // no op
+ }
+
+ @Override
+ public void stop() {
+ // no op
+ }
+
+ @Override
+ public void transferTo(String routeId) {
+ // no op
+ }
+
+ @Override
+ public MediaRoute2Info getSelectedRoute() {
+ // no op
+ return null;
+ }
+
+ @Override
+ public List getTransferableRoutes() {
+ // no op
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List getAllBluetoothRoutes() {
+ // no op
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean updateVolumeForDevices(int devices, int volume) {
+ // no op
+ return false;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
similarity index 93%
rename from services/core/java/com/android/server/media/BluetoothRouteProvider.java
rename to services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
index ab59f87e4a9c1..7979d2a3fb3bd 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
@@ -27,7 +27,6 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothLeAudio;
-import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -50,10 +49,9 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
-class BluetoothRouteProvider {
+class LegacyBluetoothRouteController implements BluetoothRouteController {
private static final String TAG = "BTRouteProvider";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -69,7 +67,7 @@ class BluetoothRouteProvider {
private final Context mContext;
private final BluetoothAdapter mBluetoothAdapter;
- private final BluetoothRoutesUpdatedListener mListener;
+ private final BluetoothRouteController.BluetoothRoutesUpdatedListener mListener;
private final AudioManager mAudioManager;
private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();
@@ -82,27 +80,8 @@ class BluetoothRouteProvider {
private BluetoothHearingAid mHearingAidProfile;
private BluetoothLeAudio mLeAudioProfile;
- /**
- * Create an instance of {@link BluetoothRouteProvider}.
- * It may return {@code null} if Bluetooth is not supported on this hardware platform.
- */
- @Nullable
- static BluetoothRouteProvider createInstance(@NonNull Context context,
- @NonNull BluetoothRoutesUpdatedListener listener) {
- Objects.requireNonNull(context);
- Objects.requireNonNull(listener);
-
- BluetoothManager bluetoothManager = (BluetoothManager)
- context.getSystemService(Context.BLUETOOTH_SERVICE);
- BluetoothAdapter btAdapter = bluetoothManager.getAdapter();
- if (btAdapter == null) {
- return null;
- }
- return new BluetoothRouteProvider(context, btAdapter, listener);
- }
-
- private BluetoothRouteProvider(Context context, BluetoothAdapter btAdapter,
- BluetoothRoutesUpdatedListener listener) {
+ LegacyBluetoothRouteController(Context context, BluetoothAdapter btAdapter,
+ BluetoothRouteController.BluetoothRoutesUpdatedListener listener) {
mContext = context;
mBluetoothAdapter = btAdapter;
mListener = listener;
@@ -119,7 +98,8 @@ class BluetoothRouteProvider {
*
* @param user {@code UserHandle} as which receiver is registered
*/
- void start(UserHandle user) {
+ @Override
+ public void start(UserHandle user) {
mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID);
mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.LE_AUDIO);
@@ -146,7 +126,8 @@ class BluetoothRouteProvider {
deviceStateChangedIntentFilter, null, null);
}
- void stop() {
+ @Override
+ public void stop() {
mContext.unregisterReceiver(mAdapterStateChangedReceiver);
mContext.unregisterReceiver(mDeviceStateChangedReceiver);
}
@@ -158,7 +139,8 @@ class BluetoothRouteProvider {
* @param routeId the id of the Bluetooth device. {@code null} denotes to clear the use of
* BT routes.
*/
- void transferTo(@Nullable String routeId) {
+ @Override
+ public void transferTo(@Nullable String routeId) {
if (routeId == null) {
clearActiveDevices();
return;
@@ -213,14 +195,16 @@ class BluetoothRouteProvider {
}
@Nullable
- MediaRoute2Info getSelectedRoute() {
+ @Override
+ public MediaRoute2Info getSelectedRoute() {
// 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).mRoute);
}
@NonNull
- List getTransferableRoutes() {
+ @Override
+ public List getTransferableRoutes() {
List routes = getAllBluetoothRoutes();
for (BluetoothRouteInfo btRoute : mActiveRoutes) {
routes.remove(btRoute.mRoute);
@@ -229,7 +213,8 @@ class BluetoothRouteProvider {
}
@NonNull
- List getAllBluetoothRoutes() {
+ @Override
+ public List getAllBluetoothRoutes() {
List routes = new ArrayList<>();
List routeIds = new ArrayList<>();
@@ -255,7 +240,8 @@ class BluetoothRouteProvider {
*
* @return true if devices can be handled by the provider.
*/
- boolean updateVolumeForDevices(int devices, int volume) {
+ @Override
+ public boolean updateVolumeForDevices(int devices, int volume) {
int routeType;
if ((devices & (AudioSystem.DEVICE_OUT_HEARING_AID)) != 0) {
routeType = MediaRoute2Info.TYPE_HEARING_AID;
@@ -415,10 +401,6 @@ class BluetoothRouteProvider {
}
}
- interface BluetoothRoutesUpdatedListener {
- void onBluetoothRoutesUpdated(@NonNull List routes);
- }
-
private static class BluetoothRouteInfo {
private BluetoothDevice mBtDevice;
private MediaRoute2Info mRoute;
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index ed76dec61845a..76ff19fcb3229 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -76,7 +76,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
private final Handler mHandler;
private final Context mContext;
private final UserHandle mUser;
- private final BluetoothRouteProvider mBtRouteProvider;
+ private final BluetoothRouteController mBtRouteProvider;
private String mSelectedRouteId;
// For apps without MODIFYING_AUDIO_ROUTING permission.
@@ -108,7 +108,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
SystemMediaRoute2Provider(Context context, UserHandle user) {
super(COMPONENT_NAME);
-
mIsSystemRouteProvider = true;
mContext = context;
mUser = user;
@@ -117,20 +116,23 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mAudioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE));
- AudioRoutesInfo newAudioRoutes = null;
- try {
- newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
- } catch (RemoteException e) {
- }
- updateDeviceRoute(newAudioRoutes);
- // .getInstance returns null if there is no bt adapter available
- mBtRouteProvider = BluetoothRouteProvider.createInstance(context, (routes) -> {
+ mBtRouteProvider = BluetoothRouteController.createInstance(context, (routes) -> {
publishProviderState();
if (updateSessionInfosIfNeeded()) {
notifySessionInfoUpdated();
}
});
+
+ AudioRoutesInfo newAudioRoutes = null;
+ try {
+ newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
+ } catch (RemoteException e) {
+ }
+
+ // The methods below should be called after all fields are initialized, as they
+ // access the fields inside.
+ updateDeviceRoute(newAudioRoutes);
updateSessionInfosIfNeeded();
}
@@ -140,23 +142,19 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
mContext.registerReceiverAsUser(mAudioReceiver, mUser,
intentFilter, null, null);
- if (mBtRouteProvider != null) {
- mHandler.post(() -> {
- mBtRouteProvider.start(mUser);
- notifyProviderState();
- });
- }
+ mHandler.post(() -> {
+ mBtRouteProvider.start(mUser);
+ notifyProviderState();
+ });
updateVolume();
}
public void stop() {
mContext.unregisterReceiver(mAudioReceiver);
- if (mBtRouteProvider != null) {
- mHandler.post(() -> {
- mBtRouteProvider.stop();
- notifyProviderState();
- });
- }
+ mHandler.post(() -> {
+ mBtRouteProvider.stop();
+ notifyProviderState();
+ });
}
@Override
@@ -218,12 +216,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
// The currently selected route is the default route.
return;
}
- if (mBtRouteProvider != null) {
- if (TextUtils.equals(routeId, mDeviceRoute.getId())) {
- mBtRouteProvider.transferTo(null);
- } else {
- mBtRouteProvider.transferTo(routeId);
- }
+ if (TextUtils.equals(routeId, mDeviceRoute.getId())) {
+ mBtRouteProvider.transferTo(null);
+ } else {
+ mBtRouteProvider.transferTo(routeId);
}
}
@@ -261,10 +257,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
SYSTEM_SESSION_ID, packageName).setSystemSession(true);
builder.addSelectedRoute(mDeviceRoute.getId());
- if (mBtRouteProvider != null) {
- for (MediaRoute2Info route : mBtRouteProvider.getAllBluetoothRoutes()) {
- builder.addTransferableRoute(route.getId());
- }
+ for (MediaRoute2Info route : mBtRouteProvider.getAllBluetoothRoutes()) {
+ builder.addTransferableRoute(route.getId());
}
return builder.setProviderId(mUniqueId).build();
}
@@ -311,10 +305,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
private void updateProviderState() {
MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
builder.addRoute(mDeviceRoute);
- if (mBtRouteProvider != null) {
- for (MediaRoute2Info route : mBtRouteProvider.getAllBluetoothRoutes()) {
- builder.addRoute(route);
- }
+ for (MediaRoute2Info route : mBtRouteProvider.getAllBluetoothRoutes()) {
+ builder.addRoute(route);
}
MediaRoute2ProviderInfo providerInfo = builder.build();
setProviderState(providerInfo);
@@ -336,12 +328,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
.setSystemSession(true);
MediaRoute2Info selectedRoute = mDeviceRoute;
- if (mBtRouteProvider != null) {
- MediaRoute2Info selectedBtRoute = mBtRouteProvider.getSelectedRoute();
- if (selectedBtRoute != null) {
- selectedRoute = selectedBtRoute;
- builder.addTransferableRoute(mDeviceRoute.getId());
- }
+ MediaRoute2Info selectedBtRoute = mBtRouteProvider.getSelectedRoute();
+ if (selectedBtRoute != null) {
+ selectedRoute = selectedBtRoute;
+ builder.addTransferableRoute(mDeviceRoute.getId());
}
mSelectedRouteId = selectedRoute.getId();
mDefaultRoute = new MediaRoute2Info.Builder(DEFAULT_ROUTE_ID, selectedRoute)
@@ -350,10 +340,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
.build();
builder.addSelectedRoute(mSelectedRouteId);
- if (mBtRouteProvider != null) {
- for (MediaRoute2Info route : mBtRouteProvider.getTransferableRoutes()) {
- builder.addTransferableRoute(route.getId());
- }
+ for (MediaRoute2Info route : mBtRouteProvider.getTransferableRoutes()) {
+ builder.addTransferableRoute(route.getId());
}
RoutingSessionInfo newSessionInfo = builder.setProviderId(mUniqueId).build();
@@ -432,7 +420,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
.build();
}
- if (mBtRouteProvider != null && mBtRouteProvider.updateVolumeForDevices(devices, volume)) {
+ if (mBtRouteProvider.updateVolumeForDevices(devices, volume)) {
return;
}
if (mDeviceVolume != volume) {