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

Commit 3d23445c authored by Alex Dadukin's avatar Alex Dadukin Committed by Android (Google) Code Review
Browse files

Merge "Conduct refactoring in BluetoothRouteProvider."

parents 0cb1e4e2 4d712db2
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) {