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

Commit 6c361f88 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Update SystemMediaRoute2Provider for multiple BT devices"

parents 89f78f9a bb3ee6e0
Loading
Loading
Loading
Loading
+321 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020 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.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.MediaRoute2Info;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseBooleanArray;

import com.android.internal.R;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

class BluetoothRouteProvider {
    private static final String TAG = "BTRouteProvider";
    private static BluetoothRouteProvider sInstance;

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>();
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    BluetoothA2dp mA2dpProfile;
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    BluetoothHearingAid mHearingAidProfile;

    private final Context mContext;
    private final BluetoothAdapter mBluetoothAdapter;
    private final BluetoothRoutesUpdatedListener mListener;
    private final Map<String, BluetoothEventReceiver> mEventReceiverMap = new HashMap<>();
    private final IntentFilter mIntentFilter = new IntentFilter();
    private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver();
    private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();

    private BluetoothDevice mActiveDevice = null;

    static synchronized BluetoothRouteProvider getInstance(@NonNull Context context,
            @NonNull BluetoothRoutesUpdatedListener listener) {
        Objects.requireNonNull(context);
        Objects.requireNonNull(listener);

        if (sInstance == null) {
            BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
            if (btAdapter == null) {
                return null;
            }
            sInstance = new BluetoothRouteProvider(context, btAdapter, listener);
        }
        return sInstance;
    }

    private BluetoothRouteProvider(Context context, BluetoothAdapter btAdapter,
            BluetoothRoutesUpdatedListener listener) {
        mContext = context;
        mBluetoothAdapter = btAdapter;
        mListener = listener;
        buildBluetoothRoutes();

        mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
        mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID);

        // Bluetooth on/off broadcasts
        addEventReceiver(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedReceiver());

        // Pairing broadcasts
        addEventReceiver(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedReceiver());

        DeviceStateChangedRecevier deviceStateChangedReceiver = new DeviceStateChangedRecevier();
        addEventReceiver(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, deviceStateChangedReceiver);
        addEventReceiver(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, deviceStateChangedReceiver);
        addEventReceiver(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,
                deviceStateChangedReceiver);
        addEventReceiver(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED,
                deviceStateChangedReceiver);

        mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, null);
    }

    private void addEventReceiver(String action, BluetoothEventReceiver eventReceiver) {
        mEventReceiverMap.put(action, eventReceiver);
        mIntentFilter.addAction(action);
    }

    private void buildBluetoothRoutes() {
        mBluetoothRoutes.clear();
        for (BluetoothDevice device : mBluetoothAdapter.getBondedDevices()) {
            if (device.isConnected()) {
                BluetoothRouteInfo newBtRoute = createBluetoothRoute(device);
                mBluetoothRoutes.put(device.getAddress(), newBtRoute);
            }
        }
    }

    @NonNull List<MediaRoute2Info> getBluetoothRoutes() {
        ArrayList<MediaRoute2Info> routes = new ArrayList<>();
        for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
            routes.add(btRoute.route);
        }
        return routes;
    }

    private void notifyBluetoothRoutesUpdated() {
        if (mListener != null) {
            mListener.onBluetoothRoutesUpdated(getBluetoothRoutes());
        }
    }

    private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
        BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo();
        newBtRoute.btDevice = device;
        newBtRoute.route = new MediaRoute2Info.Builder(device.getAddress(), device.getName())
                .addFeature(SystemMediaRoute2Provider.TYPE_LIVE_AUDIO)
                .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
                .setDescription(mContext.getResources().getText(
                        R.string.bluetooth_a2dp_audio_route_name).toString())
                .build();
        newBtRoute.connectedProfiles = new SparseBooleanArray();
        return newBtRoute;
    }

    private void setRouteConnectionStateForDevice(BluetoothDevice device,
            @MediaRoute2Info.ConnectionState int state) {
        if (device == null) {
            Log.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");
            return;
        }
        if (btRoute.route.getConnectionState() != state) {
            btRoute.route = new MediaRoute2Info.Builder(btRoute.route)
                    .setConnectionState(state).build();
        }
    }

    interface BluetoothRoutesUpdatedListener {
        void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes);
    }

    private class BluetoothRouteInfo {
        public BluetoothDevice btDevice;
        public MediaRoute2Info route;
        public SparseBooleanArray connectedProfiles;
    }

    // These callbacks run on the main thread.
    private final class BluetoothProfileListener implements BluetoothProfile.ServiceListener {
        public void onServiceConnected(int profile, BluetoothProfile proxy) {
            switch (profile) {
                case BluetoothProfile.A2DP:
                    mA2dpProfile = (BluetoothA2dp) proxy;
                    break;
                case BluetoothProfile.HEARING_AID:
                    mHearingAidProfile = (BluetoothHearingAid) proxy;
                    break;
                default:
                    return;
            }
            for (BluetoothDevice device : proxy.getConnectedDevices()) {
                BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
                if (btRoute == null) {
                    btRoute = createBluetoothRoute(device);
                    mBluetoothRoutes.put(device.getAddress(), btRoute);
                }
                btRoute.connectedProfiles.put(profile, true);
            }
        }

        public void onServiceDisconnected(int profile) {
            switch (profile) {
                case BluetoothProfile.A2DP:
                    mA2dpProfile = null;
                    break;
                case BluetoothProfile.HEARING_AID:
                    mHearingAidProfile = null;
                    break;
                default:
                    return;
            }
        }
    }
    private class BluetoothBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

            BluetoothEventReceiver receiver = mEventReceiverMap.get(action);
            if (receiver != null) {
                receiver.onReceive(context, intent, device);
            }
        }
    }

    private interface BluetoothEventReceiver {
        void onReceive(Context context, Intent intent, BluetoothDevice device);
    }

    private class AdapterStateChangedReceiver implements BluetoothEventReceiver {
        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
            if (state == BluetoothAdapter.STATE_OFF
                    || state == BluetoothAdapter.STATE_TURNING_OFF) {
                mBluetoothRoutes.clear();
                notifyBluetoothRoutesUpdated();
            } else if (state == BluetoothAdapter.STATE_ON) {
                buildBluetoothRoutes();
                if (!mBluetoothRoutes.isEmpty()) {
                    notifyBluetoothRoutesUpdated();
                }
            }
        }
    }

    private class BondStateChangedReceiver implements BluetoothEventReceiver {
        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
            int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
                    BluetoothDevice.ERROR);
            BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
            if (bondState == BluetoothDevice.BOND_BONDED && btRoute == null) {
                btRoute = createBluetoothRoute(device);
                if (mA2dpProfile != null && mA2dpProfile.getConnectedDevices().contains(device)) {
                    btRoute.connectedProfiles.put(BluetoothProfile.A2DP, true);
                }
                if (mHearingAidProfile != null
                        && mHearingAidProfile.getConnectedDevices().contains(device)) {
                    btRoute.connectedProfiles.put(BluetoothProfile.HEARING_AID, true);
                }
                mBluetoothRoutes.put(device.getAddress(), btRoute);
                notifyBluetoothRoutesUpdated();
            } else if (bondState == BluetoothDevice.BOND_NONE
                    && mBluetoothRoutes.remove(device.getAddress()) != null) {
                notifyBluetoothRoutesUpdated();
            }
        }
    }

    private class DeviceStateChangedRecevier implements BluetoothEventReceiver {
        @Override
        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
            switch (intent.getAction()) {
                case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
                    String prevActiveDeviceAddress =
                            (mActiveDevice == null) ? null : mActiveDevice.getAddress();
                    String curActiveDeviceAddress =
                            (device == null) ? null : device.getAddress();
                    if (!TextUtils.equals(prevActiveDeviceAddress, curActiveDeviceAddress)) {
                        if (mActiveDevice != null) {
                            setRouteConnectionStateForDevice(mActiveDevice,
                                    MediaRoute2Info.CONNECTION_STATE_DISCONNECTED);
                        }
                        if (device != null) {
                            setRouteConnectionStateForDevice(device,
                                    MediaRoute2Info.CONNECTION_STATE_CONNECTED);
                        }
                        notifyBluetoothRoutesUpdated();
                        mActiveDevice = device;
                    }
                    break;
                case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
                    handleConnectionStateChanged(BluetoothProfile.A2DP, intent, device);
                    break;
            }
        }

        private void handleConnectionStateChanged(int profile, Intent intent,
                BluetoothDevice device) {
            int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
            BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
            if (state == BluetoothProfile.STATE_CONNECTED) {
                if (btRoute == null) {
                    btRoute = createBluetoothRoute(device);
                    mBluetoothRoutes.put(device.getAddress(), btRoute);
                    btRoute.connectedProfiles.put(profile, true);
                    notifyBluetoothRoutesUpdated();
                } else {
                    btRoute.connectedProfiles.put(profile, true);
                }
            } else if (state == BluetoothProfile.STATE_DISCONNECTING
                    || state == BluetoothProfile.STATE_DISCONNECTED) {
                btRoute.connectedProfiles.delete(profile);
                if (btRoute.connectedProfiles.size() == 0) {
                    mBluetoothRoutes.remove(device.getAddress());
                    if (mActiveDevice != null
                            && TextUtils.equals(mActiveDevice.getAddress(), device.getAddress())) {
                        mActiveDevice = null;
                    }
                    notifyBluetoothRoutesUpdated();
                }
            }
        }
    }
}
+9 −1
Original line number Diff line number Diff line
@@ -72,7 +72,7 @@ abstract class MediaRoute2Provider {
        return mSessionInfos;
    }

    void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo,
    void setProviderState(MediaRoute2ProviderInfo providerInfo,
            List<RoutingSessionInfo> sessionInfos) {
        if (providerInfo == null) {
            mProviderInfo = null;
@@ -89,12 +89,20 @@ abstract class MediaRoute2Provider {
                            .build());
        }
        mSessionInfos = sessionInfoWithProviderId;
    }

    void notifyProviderState() {
        if (mCallback != null) {
            mCallback.onProviderStateChanged(this);
        }
    }

    void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo,
            List<RoutingSessionInfo> sessionInfos) {
        setProviderState(providerInfo, sessionInfos);
        notifyProviderState();
    }

    public boolean hasComponentName(String packageName, String className) {
        return mComponentName.getPackageName().equals(packageName)
                && mComponentName.getClassName().equals(className);
+19 −23
Original line number Diff line number Diff line
@@ -30,12 +30,12 @@ import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.Log;

import com.android.internal.R;

import java.util.Collections;
import java.util.List;

/**
 * Provides routes for local playbacks such as phone speaker, wired headset, or Bluetooth speakers.
@@ -55,6 +55,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
    private final IAudioService mAudioService;
    private final Handler mHandler;
    private final Context mContext;
    private final BluetoothRouteProvider mBtRouteProvider;

    private static ComponentName sComponentName = new ComponentName(
            SystemMediaRoute2Provider.class.getPackageName$(),
@@ -62,7 +63,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {

    //TODO: Clean up these when audio manager support multiple bt devices
    MediaRoute2Info mDefaultRoute;
    MediaRoute2Info mBluetoothA2dpRoute;
    @NonNull List<MediaRoute2Info> mBluetoothRoutes = Collections.EMPTY_LIST;
    final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();

    final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
@@ -87,6 +88,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
        mAudioService = IAudioService.Stub.asInterface(
                ServiceManager.getService(Context.AUDIO_SERVICE));

        mBtRouteProvider = BluetoothRouteProvider.getInstance(context, (routes) -> {
            mBluetoothRoutes = routes;
            publishRoutes();
        });
        initializeRoutes();
    }

@@ -157,7 +162,15 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
            updateAudioRoutes(newAudioRoutes);
        }

        publishRoutes();
        mBluetoothRoutes = mBtRouteProvider.getBluetoothRoutes();

        MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
        builder.addRoute(mDefaultRoute);
        for (MediaRoute2Info route : mBluetoothRoutes) {
            builder.addRoute(route);
        }
        setProviderState(builder.build(), Collections.emptyList());
        mHandler.post(() -> notifyProviderState());
    }

    void updateAudioRoutes(AudioRoutesInfo newRoutes) {
@@ -185,21 +198,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
                .addFeature(TYPE_LIVE_VIDEO)
                .build();

        if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
            mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
            if (mCurAudioRoutesInfo.bluetoothName != null) {
                //TODO: mark as bluetooth once MediaRoute2Info has device type
                mBluetoothA2dpRoute = new MediaRoute2Info.Builder(BLUETOOTH_ROUTE_ID,
                        mCurAudioRoutesInfo.bluetoothName)
                        .setDescription(mContext.getResources().getText(
                                R.string.bluetooth_a2dp_audio_route_name).toString())
                        .addFeature(TYPE_LIVE_AUDIO)
                        .build();
            } else {
                mBluetoothA2dpRoute = null;
            }
        }

        publishRoutes();
    }

@@ -207,15 +205,13 @@ 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.
     *
     * TODO: Support multiple BT devices
     */
    void publishRoutes() {
        MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
        if (mBluetoothA2dpRoute != null) {
            builder.addRoute(mBluetoothA2dpRoute);
        }
        builder.addRoute(mDefaultRoute);
        for (MediaRoute2Info route : mBluetoothRoutes) {
            builder.addRoute(route);
        }
        setAndNotifyProviderState(builder.build(), Collections.emptyList());
    }
}