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

Commit 03d44ce8 authored by Pavlin Radoslavov's avatar Pavlin Radoslavov
Browse files

Add Settings support for Bluetooth Multi-A2DP and Multi-HFP

When there are multiple connected A2DP/HFP devices, if a connected
device's name is clicked on, that device will be chosen as
Active - i.e., it will be the device chosen for audio out / phone call.

Also:
 * Listen to the BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED
   and BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED intents
   and update the the status of the current active device.
 * When connecting a new device, and Multi-A2DP is enabled, don't
   disconnect the currently connected device.
 * Update the implementation of isA2dpPlaying() so it correctly checks
   all connected devices, not only the first one.

Test: Manual: multiple connected A2DP devices, and selecting each as
      the Active Device.
Bug: 64767509

Change-Id: I69f3c85ebf5a7f07f6deed484c6dd65705460ae4
Merged-In: I69f3c85ebf5a7f07f6deed484c6dd65705460ae4
(cherry picked from commit 1af33a19)
parent 65609651
Loading
Loading
Loading
Loading
+23 −9
Original line number Original line Diff line number Diff line
@@ -129,6 +129,9 @@ public class A2dpProfile implements LocalBluetoothProfile {


    public boolean connect(BluetoothDevice device) {
    public boolean connect(BluetoothDevice device) {
        if (mService == null) return false;
        if (mService == null) return false;
        int max_connected_devices = mLocalAdapter.getMaxConnectedAudioDevices();
        if (max_connected_devices == 1) {
            // Original behavior: disconnect currently connected device
            List<BluetoothDevice> sinks = getConnectedDevices();
            List<BluetoothDevice> sinks = getConnectedDevices();
            if (sinks != null) {
            if (sinks != null) {
                for (BluetoothDevice sink : sinks) {
                for (BluetoothDevice sink : sinks) {
@@ -139,6 +142,7 @@ public class A2dpProfile implements LocalBluetoothProfile {
                    mService.disconnect(sink);
                    mService.disconnect(sink);
                }
                }
            }
            }
        }
        return mService.connect(device);
        return mService.connect(device);
    }
    }


@@ -158,6 +162,16 @@ public class A2dpProfile implements LocalBluetoothProfile {
        return mService.getConnectionState(device);
        return mService.getConnectionState(device);
    }
    }


    public boolean setActiveDevice(BluetoothDevice device) {
        if (mService == null) return false;
        return mService.setActiveDevice(device);
    }

    public BluetoothDevice getActiveDevice() {
        if (mService == null) return null;
        return mService.getActiveDevice();
    }

    public boolean isPreferred(BluetoothDevice device) {
    public boolean isPreferred(BluetoothDevice device) {
        if (mService == null) return false;
        if (mService == null) return false;
        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
@@ -181,8 +195,8 @@ public class A2dpProfile implements LocalBluetoothProfile {
    boolean isA2dpPlaying() {
    boolean isA2dpPlaying() {
        if (mService == null) return false;
        if (mService == null) return false;
        List<BluetoothDevice> sinks = mService.getConnectedDevices();
        List<BluetoothDevice> sinks = mService.getConnectedDevices();
        if (!sinks.isEmpty()) {
        for (BluetoothDevice device : sinks) {
            if (mService.isA2dpPlaying(sinks.get(0))) {
            if (mService.isA2dpPlaying(device)) {
                return true;
                return true;
            }
            }
        }
        }
+1 −0
Original line number Original line Diff line number Diff line
@@ -28,4 +28,5 @@ public interface BluetoothCallback {
    void onDeviceDeleted(CachedBluetoothDevice cachedDevice);
    void onDeviceDeleted(CachedBluetoothDevice cachedDevice);
    void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState);
    void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState);
    void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state);
    void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state);
    void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile);
}
}
+41 −0
Original line number Original line Diff line number Diff line
@@ -16,9 +16,12 @@


package com.android.settingslib.bluetooth;
package com.android.settingslib.bluetooth;


import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
@@ -31,6 +34,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Set;


/**
/**
@@ -106,6 +110,12 @@ public class BluetoothEventManager {
        // Dock event broadcasts
        // Dock event broadcasts
        addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());
        addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());


        // Active device broadcasts
        addHandler(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED,
                   new ActiveDeviceChangedHandler());
        addHandler(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED,
                   new ActiveDeviceChangedHandler());

        mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
        mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
        mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
        mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
    }
    }
@@ -409,4 +419,35 @@ public class BluetoothEventManager {


        return deviceAdded;
        return deviceAdded;
    }
    }

    private class ActiveDeviceChangedHandler implements Handler {
        @Override
        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
            String action = intent.getAction();
            if (action == null) {
                Log.w(TAG, "ActiveDeviceChangedHandler: action is null");
                return;
            }
            CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device);
            int bluetoothProfile = 0;
            if (Objects.equals(action, BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
                bluetoothProfile = BluetoothProfile.A2DP;
            } else if (Objects.equals(action, BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
                bluetoothProfile = BluetoothProfile.HEADSET;
            } else {
                Log.w(TAG, "ActiveDeviceChangedHandler: unknown action " + action);
                return;
            }
            dispatchActiveDeviceChanged(activeDevice, bluetoothProfile);
        }
    }

    private void dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice,
                                             int bluetoothProfile) {
        synchronized (mCallbacks) {
            for (BluetoothCallback callback : mCallbacks) {
                callback.onActiveDeviceChanged(activeDevice, bluetoothProfile);
            }
        }
    }
}
}
+75 −8
Original line number Original line Diff line number Diff line
@@ -105,6 +105,10 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
    private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000;
    private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000;
    private static final long MAX_HOGP_DELAY_FOR_AUTO_CONNECT = 30000;
    private static final long MAX_HOGP_DELAY_FOR_AUTO_CONNECT = 30000;


    // Active device state
    private boolean mIsActiveDeviceA2dp = false;
    private boolean mIsActiveDeviceHeadset = false;

    /**
    /**
     * Describes the current device and profile for logging.
     * Describes the current device and profile for logging.
     *
     *
@@ -156,6 +160,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
            mRemovedProfiles.add(profile);
            mRemovedProfiles.add(profile);
            mLocalNapRoleConnected = false;
            mLocalNapRoleConnected = false;
        }
        }
        fetchActiveDevices();
    }
    }


    CachedBluetoothDevice(Context context,
    CachedBluetoothDevice(Context context,
@@ -359,6 +364,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
        fetchName();
        fetchName();
        fetchBtClass();
        fetchBtClass();
        updateProfiles();
        updateProfiles();
        fetchActiveDevices();
        migratePhonebookPermissionChoice();
        migratePhonebookPermissionChoice();
        migrateMessagePermissionChoice();
        migrateMessagePermissionChoice();
        fetchMessageRejectionCount();
        fetchMessageRejectionCount();
@@ -454,6 +460,33 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
        return mDevice.getBondState();
        return mDevice.getBondState();
    }
    }


    /**
     * Set the device status as active or non-active per Bluetooth profile.
     *
     * @param isActive true if the device is active
     * @param bluetoothProfile the Bluetooth profile
     */
    public void setActiveDevice(boolean isActive, int bluetoothProfile) {
        boolean changed = false;
        switch (bluetoothProfile) {
        case BluetoothProfile.A2DP:
            changed = (mIsActiveDeviceA2dp != isActive);
            mIsActiveDeviceA2dp = isActive;
            break;
        case BluetoothProfile.HEADSET:
            changed = (mIsActiveDeviceHeadset != isActive);
            mIsActiveDeviceHeadset = isActive;
            break;
        default:
            Log.w(TAG, "setActiveDevice: unknown profile " + bluetoothProfile +
                    " isActive " + isActive);
            break;
        }
        if (changed) {
            dispatchAttributesChanged();
        }
    }

    void setRssi(short rssi) {
    void setRssi(short rssi) {
        if (mRssi != rssi) {
        if (mRssi != rssi) {
            mRssi = rssi;
            mRssi = rssi;
@@ -529,6 +562,17 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
        return true;
        return true;
    }
    }


    private void fetchActiveDevices() {
        A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
        if (a2dpProfile != null) {
            mIsActiveDeviceA2dp = mDevice.equals(a2dpProfile.getActiveDevice());
        }
        HeadsetProfile headsetProfile = mProfileManager.getHeadsetProfile();
        if (headsetProfile != null) {
            mIsActiveDeviceHeadset = mDevice.equals(headsetProfile.getActiveDevice());
        }
    }

    /**
    /**
     * Refreshes the UI for the BT class, including fetching the latest value
     * Refreshes the UI for the BT class, including fetching the latest value
     * for the class.
     * for the class.
@@ -896,37 +940,60 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
                    com.android.settingslib.Utils.formatPercentage(batteryLevel);
                    com.android.settingslib.Utils.formatPercentage(batteryLevel);
        }
        }


        // TODO: A temporary workaround solution using string description the device is active.
        // Issue tracked by b/72317067 .
        // An alternative solution would be visual indication.
        // Intentionally not adding the strings to strings.xml for now:
        //  1) If this is just a short-term solution, no need to waste translation effort
        //  2) The number of strings with all possible combinations becomes enormously large.
        // If string description becomes part of the final solution, we MUST NOT
        // concatenate the strings here: this does not translate well.
        String activeString = null;
        if (mIsActiveDeviceA2dp && mIsActiveDeviceHeadset) {
            activeString = ", active";
        } else {
            if (mIsActiveDeviceA2dp) {
                activeString = ", active(media)";
            }
            if (mIsActiveDeviceHeadset) {
                activeString = ", active(phone)";
            }
        }
        if (activeString == null) activeString = "";

        if (profileConnected) {
        if (profileConnected) {
            if (a2dpNotConnected && hfpNotConnected) {
            if (a2dpNotConnected && hfpNotConnected) {
                if (batteryLevelPercentageString != null) {
                if (batteryLevelPercentageString != null) {
                    return mContext.getString(
                    return mContext.getString(
                            R.string.bluetooth_connected_no_headset_no_a2dp_battery_level,
                            R.string.bluetooth_connected_no_headset_no_a2dp_battery_level,
                            batteryLevelPercentageString);
                            batteryLevelPercentageString) + activeString;
                } else {
                } else {
                    return mContext.getString(R.string.bluetooth_connected_no_headset_no_a2dp);
                    return mContext.getString(R.string.bluetooth_connected_no_headset_no_a2dp) +
                        activeString;
                }
                }


            } else if (a2dpNotConnected) {
            } else if (a2dpNotConnected) {
                if (batteryLevelPercentageString != null) {
                if (batteryLevelPercentageString != null) {
                    return mContext.getString(R.string.bluetooth_connected_no_a2dp_battery_level,
                    return mContext.getString(R.string.bluetooth_connected_no_a2dp_battery_level,
                            batteryLevelPercentageString);
                            batteryLevelPercentageString) + activeString;
                } else {
                } else {
                    return mContext.getString(R.string.bluetooth_connected_no_a2dp);
                    return mContext.getString(R.string.bluetooth_connected_no_a2dp) + activeString;
                }
                }


            } else if (hfpNotConnected) {
            } else if (hfpNotConnected) {
                if (batteryLevelPercentageString != null) {
                if (batteryLevelPercentageString != null) {
                    return mContext.getString(R.string.bluetooth_connected_no_headset_battery_level,
                    return mContext.getString(R.string.bluetooth_connected_no_headset_battery_level,
                            batteryLevelPercentageString);
                            batteryLevelPercentageString) + activeString;
                } else {
                } else {
                    return mContext.getString(R.string.bluetooth_connected_no_headset);
                    return mContext.getString(R.string.bluetooth_connected_no_headset)
                          + activeString;
                }
                }
            } else {
            } else {
                if (batteryLevelPercentageString != null) {
                if (batteryLevelPercentageString != null) {
                    return mContext.getString(R.string.bluetooth_connected_battery_level,
                    return mContext.getString(R.string.bluetooth_connected_battery_level,
                            batteryLevelPercentageString);
                            batteryLevelPercentageString) + activeString;
                } else {
                } else {
                    return mContext.getString(R.string.bluetooth_connected);
                    return mContext.getString(R.string.bluetooth_connected) + activeString;
                }
                }
            }
            }
        }
        }
+10 −0
Original line number Original line Diff line number Diff line
@@ -153,6 +153,16 @@ public class HeadsetProfile implements LocalBluetoothProfile {
        return BluetoothProfile.STATE_DISCONNECTED;
        return BluetoothProfile.STATE_DISCONNECTED;
    }
    }


    public boolean setActiveDevice(BluetoothDevice device) {
        if (mService == null) return false;
        return mService.setActiveDevice(device);
    }

    public BluetoothDevice getActiveDevice() {
        if (mService == null) return null;
        return mService.getActiveDevice();
    }

    public boolean isPreferred(BluetoothDevice device) {
    public boolean isPreferred(BluetoothDevice device) {
        if (mService == null) return false;
        if (mService == null) return false;
        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
Loading