Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4d712db2 authored by Alex Dadukin's avatar Alex Dadukin
Browse files

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
parent 15ea9319
Loading
Loading
Loading
Loading
+176 −0
Original line number Diff line number Diff line
/*
 * 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}.
     *
     * <p>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.
     *
     * <p>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.
     *
     * <p>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<MediaRoute2Info> getTransferableRoutes();

    /**
     * Provides all connected Bluetooth routes.
     *
     * @return list of Bluetooth routes or an empty list.
     */
    @NonNull
    List<MediaRoute2Info> 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<MediaRoute2Info> routes);
    }

    /**
     * No-op implementation of {@link BluetoothRouteController}.
     *
     * <p>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<MediaRoute2Info> getTransferableRoutes() {
            // no op
            return Collections.emptyList();
        }

        @Override
        public List<MediaRoute2Info> getAllBluetoothRoutes() {
            // no op
            return Collections.emptyList();
        }

        @Override
        public boolean updateVolumeForDevices(int devices, int volume) {
            // no op
            return false;
        }
    }
}
+18 −36
Original line number Diff line number Diff line
@@ -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<MediaRoute2Info> getTransferableRoutes() {
    @Override
    public List<MediaRoute2Info> getTransferableRoutes() {
        List<MediaRoute2Info> routes = getAllBluetoothRoutes();
        for (BluetoothRouteInfo btRoute : mActiveRoutes) {
            routes.remove(btRoute.mRoute);
@@ -229,7 +213,8 @@ class BluetoothRouteProvider {
    }

    @NonNull
    List<MediaRoute2Info> getAllBluetoothRoutes() {
    @Override
    public List<MediaRoute2Info> getAllBluetoothRoutes() {
        List<MediaRoute2Info> routes = new ArrayList<>();
        List<String> 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<MediaRoute2Info> routes);
    }

    private static class BluetoothRouteInfo {
        private BluetoothDevice mBtDevice;
        private MediaRoute2Info mRoute;
+35 −47
Original line number Diff line number Diff line
@@ -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,24 +142,20 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
        mContext.registerReceiverAsUser(mAudioReceiver, mUser,
                intentFilter, null, null);

        if (mBtRouteProvider != null) {
        mHandler.post(() -> {
            mBtRouteProvider.start(mUser);
            notifyProviderState();
        });
        }
        updateVolume();
    }

    public void stop() {
        mContext.unregisterReceiver(mAudioReceiver);
        if (mBtRouteProvider != null) {
        mHandler.post(() -> {
            mBtRouteProvider.stop();
            notifyProviderState();
        });
    }
    }

    @Override
    public void setCallback(Callback callback) {
@@ -218,14 +216,12 @@ 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);
        }
    }
    }

    @Override
    public void setRouteVolume(long requestId, String routeId, int volume) {
@@ -261,11 +257,9 @@ 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());
            }
            }
            return builder.setProviderId(mUniqueId).build();
        }
    }
@@ -311,11 +305,9 @@ 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);
        }
        }
        MediaRoute2ProviderInfo providerInfo = builder.build();
        setProviderState(providerInfo);
        if (DEBUG) {
@@ -336,13 +328,11 @@ 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());
            }
            }
            mSelectedRouteId = selectedRoute.getId();
            mDefaultRoute = new MediaRoute2Info.Builder(DEFAULT_ROUTE_ID, selectedRoute)
                    .setSystemRoute(true)
@@ -350,11 +340,9 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
                    .build();
            builder.addSelectedRoute(mSelectedRouteId);

            if (mBtRouteProvider != null) {
            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) {