From 4d712db2e0f97881488c750d020095f322bab74a Mon Sep 17 00:00:00 2001 From: Alex Dadukin Date: Mon, 13 Feb 2023 14:58:19 +0000 Subject: [PATCH] Conduct refactoring in BluetoothRouteProvider. - BluetoothRouteProvider has been renamed to LegacyBluetoothRouteController - Extracted interface from BluetoothRouteProvider to make granular changes - Provide NoOp implementation to reduce null checks Bug: b/264876378 Test: tested manually Change-Id: Ibb32ba3a0825fff45ded460f747b3c3b2d14c2dd --- .../media/BluetoothRouteController.java | 176 ++++++++++++++++++ ...va => LegacyBluetoothRouteController.java} | 54 ++---- .../media/SystemMediaRoute2Provider.java | 82 ++++---- 3 files changed, 229 insertions(+), 83 deletions(-) create mode 100644 services/core/java/com/android/server/media/BluetoothRouteController.java rename services/core/java/com/android/server/media/{BluetoothRouteProvider.java => LegacyBluetoothRouteController.java} (93%) 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) {