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

Commit c6db129e authored by Mariusz Skamra's avatar Mariusz Skamra Committed by Jack He
Browse files

Add support for Generic Telephone Bearer service (GTBS)

This patch integrates Telecomm service to GTBS

Bug: 150670922
Tag: #feature
Sponsor: jpawlowski@
Test: atest BluetoothInstrumentationTests TelecomUnitTests
Change-Id: Idb94f73aa39757d6ad8a90a6a12f47c5491f9a0d
parent bb070c31
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -315,6 +315,9 @@
            <intent-filter>
                <action android:name="android.bluetooth.IBluetoothHeadsetPhone"/>
            </intent-filter>
            <intent-filter>
                <action android:name="android.bluetooth.IBluetoothLeCallControlCallback" />
            </intent-filter>
        </service>

        <service android:name=".components.TelecomService"
+2 −2
Original line number Diff line number Diff line
@@ -875,8 +875,8 @@ public class CallAudioRouteStateMachine extends StateMachine {
                    return HANDLED;
                case SWITCH_FOCUS:
                    if (msg.arg1 == NO_FOCUS) {
                        // Only disconnect SCO audio here instead of routing away from BT entirely.
                        mBluetoothRouteManager.disconnectSco();
                        // Only disconnect audio here instead of routing away from BT entirely.
                        mBluetoothRouteManager.disconnectAudio();
                        reinitialize();
                        mCallAudioManager.notifyAudioOperationsComplete();
                    } else if (msg.arg1 == RINGING_FOCUS
+149 −23
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.telecom.Log;
@@ -37,6 +38,11 @@ import java.util.List;
import java.util.Set;

public class BluetoothDeviceManager {

    public static final int DEVICE_TYPE_HEADSET = 0;
    public static final int DEVICE_TYPE_HEARING_AID = 1;
    public static final int DEVICE_TYPE_LE_AUDIO = 2;

    private final BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
            new BluetoothProfile.ServiceListener() {
                @Override
@@ -52,6 +58,10 @@ public class BluetoothDeviceManager {
                                mBluetoothHearingAid = (BluetoothHearingAid) proxy;
                                logString = "Got BluetoothHearingAid: "
                                        + mBluetoothHearingAid;
                            } else if (profile == BluetoothProfile.LE_AUDIO) {
                                mBluetoothLeAudioService = (BluetoothLeAudio) proxy;
                                logString = "Got BluetoothLeAudio: "
                                        + mBluetoothLeAudioService;
                            } else {
                                logString = "Connected to non-requested bluetooth service." +
                                        " Not changing bluetooth headset.";
@@ -74,7 +84,8 @@ public class BluetoothDeviceManager {
                            if (profile == BluetoothProfile.HEADSET) {
                                mBluetoothHeadset = null;
                                lostServiceDevices = mHfpDevicesByAddress;
                                mBluetoothRouteManager.onActiveDeviceChanged(null, false);
                                mBluetoothRouteManager.onActiveDeviceChanged(null,
                                        DEVICE_TYPE_HEADSET);
                                logString = "Lost BluetoothHeadset service. " +
                                        "Removing all tracked devices";
                            } else if (profile == BluetoothProfile.HEARING_AID) {
@@ -82,7 +93,15 @@ public class BluetoothDeviceManager {
                                logString = "Lost BluetoothHearingAid service. " +
                                        "Removing all tracked devices.";
                                lostServiceDevices = mHearingAidDevicesByAddress;
                                mBluetoothRouteManager.onActiveDeviceChanged(null, true);
                                mBluetoothRouteManager.onActiveDeviceChanged(null,
                                        DEVICE_TYPE_HEARING_AID);
                            } else if (profile == BluetoothProfile.LE_AUDIO) {
                                mBluetoothLeAudioService = null;
                                logString = "Lost BluetoothLeAudio service. " +
                                        "Removing all tracked devices.";
                                lostServiceDevices = mLeAudioDevicesByAddress;
                                mBluetoothRouteManager.onActiveDeviceChanged(null,
                                        DEVICE_TYPE_LE_AUDIO);
                            } else {
                                return;
                            }
@@ -108,6 +127,12 @@ public class BluetoothDeviceManager {
            new LinkedHashMap<>();
    private final LinkedHashMap<BluetoothDevice, Long> mHearingAidDeviceSyncIds =
            new LinkedHashMap<>();
    private final LinkedHashMap<String, BluetoothDevice> mLeAudioDevicesByAddress =
            new LinkedHashMap<>();
    private final LinkedHashMap<BluetoothDevice, Integer> mGroupsByDevice =
            new LinkedHashMap<>();
    private int mGroupIdActive = BluetoothLeAudio.GROUP_ID_INVALID;
    private int mGroupIdPending = BluetoothLeAudio.GROUP_ID_INVALID;
    private final LocalLog mLocalLog = new LocalLog(20);

    // This lock only protects internal state -- it doesn't lock on anything going into Telecom.
@@ -116,6 +141,7 @@ public class BluetoothDeviceManager {
    private BluetoothRouteManager mBluetoothRouteManager;
    private BluetoothHeadset mBluetoothHeadset;
    private BluetoothHearingAid mBluetoothHearingAid;
    private BluetoothLeAudio mBluetoothLeAudioService;
    private BluetoothDevice mBluetoothHearingAidActiveDeviceCache;
    private BluetoothAdapter mBluetoothAdapter;

@@ -126,6 +152,8 @@ public class BluetoothDeviceManager {
                    BluetoothProfile.HEADSET);
            bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
                    BluetoothProfile.HEARING_AID);
            bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
                    BluetoothProfile.LE_AUDIO);
        }
    }

@@ -133,9 +161,20 @@ public class BluetoothDeviceManager {
        mBluetoothRouteManager = brm;
    }

    private List<BluetoothDevice> getLeAudioConnectedDevices() {
        synchronized (mLock) {
            // Filter out disconnected devices and/or those that have no group assigned
            ArrayList<BluetoothDevice> devices = new ArrayList<>(mGroupsByDevice.keySet());
            devices.removeIf(device -> !mLeAudioDevicesByAddress.containsValue(device));
            return devices;
        }
    }

    public int getNumConnectedDevices() {
        synchronized (mLock) {
            return mHfpDevicesByAddress.size() + mHearingAidDevicesByAddress.size();
            return mHfpDevicesByAddress.size() +
                    mHearingAidDevicesByAddress.size() +
                    getLeAudioConnectedDevices().size();
        }
    }

@@ -143,6 +182,7 @@ public class BluetoothDeviceManager {
        synchronized (mLock) {
            ArrayList<BluetoothDevice> result = new ArrayList<>(mHfpDevicesByAddress.values());
            result.addAll(mHearingAidDevicesByAddress.values());
            result.addAll(getLeAudioConnectedDevices());
            return Collections.unmodifiableCollection(result);
        }
    }
@@ -177,6 +217,31 @@ public class BluetoothDeviceManager {
                seenHiSyncIds.add(hiSyncId);
            }
        }

        Set<Integer> seenGroupIds = new LinkedHashSet<>();
        if (mBluetoothAdapter != null) {
            for (BluetoothDevice device : mBluetoothAdapter.getActiveDevices(
                        BluetoothProfile.LE_AUDIO)) {
                if (device != null) {
                    result.add(device);
                    seenGroupIds.add(mGroupsByDevice.getOrDefault(device, -1));
                    break;
                }
            }
        }
        synchronized (mLock) {
            for (BluetoothDevice d : getLeAudioConnectedDevices()) {
                int groupId = mGroupsByDevice.getOrDefault(d,
                        BluetoothLeAudio.GROUP_ID_INVALID);
                if (groupId == BluetoothLeAudio.GROUP_ID_INVALID
                        || seenGroupIds.contains(groupId)) {
                    continue;
                }
                result.add(d);
                seenGroupIds.add(groupId);
            }
        }

        return Collections.unmodifiableCollection(result);
    }

@@ -192,6 +257,10 @@ public class BluetoothDeviceManager {
        return mBluetoothHearingAid;
    }

    public BluetoothLeAudio getLeAudioService() {
        return mBluetoothLeAudioService;
    }

    public void setHeadsetServiceForTesting(BluetoothHeadset bluetoothHeadset) {
        mBluetoothHeadset = bluetoothHeadset;
    }
@@ -200,12 +269,33 @@ public class BluetoothDeviceManager {
        mBluetoothHearingAid = bluetoothHearingAid;
    }

    void onDeviceConnected(BluetoothDevice device, boolean isHearingAid) {
        mLocalLog.log("Device connected -- address: " + device.getAddress() + " isHeadingAid: "
                + isHearingAid);
    public void setLeAudioServiceForTesting(BluetoothLeAudio bluetoothLeAudio) {
        mBluetoothLeAudioService = bluetoothLeAudio;
    }

    public static String getDeviceTypeString(int deviceType) {
        switch (deviceType) {
            case DEVICE_TYPE_LE_AUDIO:
                return "LeAudio";
            case DEVICE_TYPE_HEARING_AID:
                return "HearingAid";
            case DEVICE_TYPE_HEADSET:
                return "HFP";
            default:
                return "unknown type";
        }
    }

    void onDeviceConnected(BluetoothDevice device, int deviceType) {
        synchronized (mLock) {
            LinkedHashMap<String, BluetoothDevice> targetDeviceMap;
            if (isHearingAid) {
            if (deviceType == DEVICE_TYPE_LE_AUDIO) {
                if (mBluetoothLeAudioService == null) {
                    Log.w(this, "LE audio service null when receiving device added broadcast");
                    return;
                }
                targetDeviceMap = mLeAudioDevicesByAddress;
            } else if (deviceType == DEVICE_TYPE_HEARING_AID) {
                if (mBluetoothHearingAid == null) {
                    Log.w(this, "Hearing aid service null when receiving device added broadcast");
                    return;
@@ -213,12 +303,16 @@ public class BluetoothDeviceManager {
                long hiSyncId = mBluetoothHearingAid.getHiSyncId(device);
                mHearingAidDeviceSyncIds.put(device, hiSyncId);
                targetDeviceMap = mHearingAidDevicesByAddress;
            } else {
            } else if (deviceType == DEVICE_TYPE_HEADSET) {
                if (mBluetoothHeadset == null) {
                    Log.w(this, "Headset service null when receiving device added broadcast");
                    return;
                }
                targetDeviceMap = mHfpDevicesByAddress;
            } else {
                Log.w(this, "Device: " + device.getAddress() + " with invalid type: "
                            + getDeviceTypeString(deviceType));
                return;
            }
            if (!targetDeviceMap.containsKey(device.getAddress())) {
                targetDeviceMap.put(device.getAddress(), device);
@@ -227,16 +321,22 @@ public class BluetoothDeviceManager {
        }
    }

    void onDeviceDisconnected(BluetoothDevice device, boolean isHearingAid) {
        mLocalLog.log("Device disconnected -- address: " + device.getAddress() + " isHeadingAid: "
                + isHearingAid);
    void onDeviceDisconnected(BluetoothDevice device, int deviceType) {
        mLocalLog.log("Device disconnected -- address: " + device.getAddress() + " deviceType: "
                + deviceType);
        synchronized (mLock) {
            LinkedHashMap<String, BluetoothDevice> targetDeviceMap;
            if (isHearingAid) {
            if (deviceType == DEVICE_TYPE_LE_AUDIO) {
                targetDeviceMap = mLeAudioDevicesByAddress;
            } else if (deviceType == DEVICE_TYPE_HEARING_AID) {
                mHearingAidDeviceSyncIds.remove(device);
                targetDeviceMap = mHearingAidDevicesByAddress;
            } else {
            } else if (deviceType == DEVICE_TYPE_HEADSET) {
                targetDeviceMap = mHfpDevicesByAddress;
            } else {
                Log.w(this, "Device: " + device.getAddress() + " with invalid type: "
                            + getDeviceTypeString(deviceType));
                return;
            }
            if (targetDeviceMap.containsKey(device.getAddress())) {
                targetDeviceMap.remove(device.getAddress());
@@ -245,17 +345,35 @@ public class BluetoothDeviceManager {
        }
    }

    public void disconnectAudio() {
        if (mBluetoothAdapter != null) {
            for (BluetoothDevice device: mBluetoothAdapter.getActiveDevices(
                        BluetoothProfile.HEARING_AID)) {
                if (device != null) {
                    mBluetoothAdapter.removeActiveDevice(BluetoothAdapter.ACTIVE_DEVICE_ALL);
    void onGroupNodeAdded(BluetoothDevice device, int groupId) {
        Log.i(this, device.getAddress() + " group added " + groupId);
        if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
            Log.w(this, "invalid parameter");
            return;
        }

        synchronized (mLock) {
            mGroupsByDevice.put(device, groupId);
        }
    }

    void onGroupNodeRemoved(BluetoothDevice device, int groupId) {
        if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
            Log.w(this, "invalid parameter");
            return;
        }

        synchronized (mLock) {
            mGroupsByDevice.remove(device);
        }
    }

    public void disconnectAudio() {
        if (mBluetoothAdapter != null) {
            mBluetoothAdapter.removeActiveDevice(BluetoothAdapter.ACTIVE_DEVICE_ALL);
            disconnectSco();
        }
    }

    public void disconnectSco() {
        if (mBluetoothHeadset == null) {
@@ -265,10 +383,18 @@ public class BluetoothDeviceManager {
        }
    }

    // Connect audio to the bluetooth device at address, checking to see whether it's a hearing aid
    // or a HFP device, and using the proper BT API.
    // Connect audio to the bluetooth device at address, checking to see whether it's
    // le audio, hearing aid or a HFP device, and using the proper BT API.
    public boolean connectAudio(String address) {
        if (mHearingAidDevicesByAddress.containsKey(address)) {
        if (mLeAudioDevicesByAddress.containsKey(address)) {
            if (mBluetoothLeAudioService == null) {
                Log.w(this, "Attempting to turn on audio when the le audio service is null");
                return false;
            }
            BluetoothDevice device = mLeAudioDevicesByAddress.get(address);
            return mBluetoothAdapter.setActiveDevice(
                    device, BluetoothAdapter.ACTIVE_DEVICE_ALL);
        } else if (mHearingAidDevicesByAddress.containsKey(address)) {
            if (mBluetoothHearingAid == null) {
                Log.w(this, "Attempting to turn on audio when the hearing aid service is null");
                return false;
+75 −25
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothLeAudio;
import android.content.Context;
import android.os.Message;
import android.telecom.Log;
@@ -35,10 +36,12 @@ import com.android.internal.util.StateMachine;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.Timeouts;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -294,7 +297,7 @@ public class BluetoothRouteManager extends StateMachine {
                        break;
                    case BT_AUDIO_IS_ON:
                        if (Objects.equals(mDeviceAddress, address)) {
                            Log.i(LOG_TAG, "HFP connection success for device %s.", mDeviceAddress);
                            Log.i(LOG_TAG, "BT connection success for device %s.", mDeviceAddress);
                            transitionTo(mAudioConnectedStates.get(mDeviceAddress));
                        } else {
                            Log.w(LOG_TAG, "In connecting state for device %s but %s" +
@@ -451,6 +454,7 @@ public class BluetoothRouteManager extends StateMachine {
    // Tracks the active devices in the BT stack (HFP or hearing aid).
    private BluetoothDevice mHfpActiveDeviceCache = null;
    private BluetoothDevice mHearingAidActiveDeviceCache = null;
    private BluetoothDevice mLeAudioActiveDeviceCache = null;
    private BluetoothDevice mMostRecentlyReportedActiveDevice = null;

    public BluetoothRouteManager(Context context, TelecomSystem.SyncRoot lock,
@@ -548,8 +552,8 @@ public class BluetoothRouteManager extends StateMachine {
        sendMessage(DISCONNECT_HFP, args);
    }

    public void disconnectSco() {
        mDeviceManager.disconnectSco();
    public void disconnectAudio() {
        mDeviceManager.disconnectAudio();
    }

    public void cacheHearingAidDevice() {
@@ -582,19 +586,37 @@ public class BluetoothRouteManager extends StateMachine {
        mListener.onBluetoothDeviceListChanged();
    }

    public void onActiveDeviceChanged(BluetoothDevice device, boolean isHearingAid) {
        boolean wasActiveDevicePresent = mHearingAidActiveDeviceCache != null
                || mHfpActiveDeviceCache != null;
        if (isHearingAid) {
    public void onAudioOn(String address) {
        Session session = Log.createSubsession();
        SomeArgs args = SomeArgs.obtain();
        args.arg1 = session;
        args.arg2 = address;
        sendMessage(BT_AUDIO_IS_ON, args);
    }

    public void onAudioLost(String address) {
        Session session = Log.createSubsession();
        SomeArgs args = SomeArgs.obtain();
        args.arg1 = session;
        args.arg2 = address;
        sendMessage(BT_AUDIO_LOST, args);
    }

    public void onActiveDeviceChanged(BluetoothDevice device, int deviceType) {
        boolean wasActiveDevicePresent = hasBtActiveDevice();
        if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) {
            mLeAudioActiveDeviceCache = device;
        } else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID) {
            mHearingAidActiveDeviceCache = device;
        } else {
        } else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEADSET) {
            mHfpActiveDeviceCache = device;
        } else {
            return;
        }

        if (device != null) mMostRecentlyReportedActiveDevice = device;

        boolean isActiveDevicePresent = mHearingAidActiveDeviceCache != null
                || mHfpActiveDeviceCache != null;
        boolean isActiveDevicePresent = hasBtActiveDevice();

        if (wasActiveDevicePresent && !isActiveDevicePresent) {
            mListener.onBluetoothActiveDeviceGone();
@@ -604,7 +626,9 @@ public class BluetoothRouteManager extends StateMachine {
    }

    public boolean hasBtActiveDevice() {
        return mHearingAidActiveDeviceCache != null || mHfpActiveDeviceCache != null;
        return mLeAudioActiveDeviceCache != null ||
                mHearingAidActiveDeviceCache != null ||
                mHfpActiveDeviceCache != null;
    }

    public Collection<BluetoothDevice> getConnectedDevices() {
@@ -682,6 +706,9 @@ public class BluetoothRouteManager extends StateMachine {
        if (mHearingAidActiveDeviceCache != null) {
            return mHearingAidActiveDeviceCache.getAddress();
        }
        if (mLeAudioActiveDeviceCache != null) {
            return mLeAudioActiveDeviceCache.getAddress();
        }
        return null;
    }

@@ -705,29 +732,33 @@ public class BluetoothRouteManager extends StateMachine {
        BluetoothAdapter bluetoothAdapter = mDeviceManager.getBluetoothAdapter();
        BluetoothHeadset bluetoothHeadset = mDeviceManager.getBluetoothHeadset();
        BluetoothHearingAid bluetoothHearingAid = mDeviceManager.getBluetoothHearingAid();
        BluetoothLeAudio bluetoothLeAudio = mDeviceManager.getLeAudioService();

        BluetoothDevice hfpAudioOnDevice = null;
        BluetoothDevice hearingAidActiveDevice = null;
        BluetoothDevice leAudioActiveDevice = null;

        if (bluetoothAdapter == null) {
            Log.i(this, "getBluetoothAudioConnectedDevice: no adapter available.");
            return null;
        }
        if (bluetoothHeadset == null && bluetoothHearingAid == null) {
        if (bluetoothHeadset == null && bluetoothHearingAid == null && bluetoothLeAudio == null) {
            Log.i(this, "getBluetoothAudioConnectedDevice: no service available.");
            return null;
        }

        int activeDevices = 0;
        if (bluetoothHeadset != null) {
            for (BluetoothDevice device : bluetoothAdapter.getActiveDevices(
                        BluetoothProfile.HEADSET)) {
                hfpAudioOnDevice = device;
                break;
            }

            if (bluetoothHeadset.getAudioState(hfpAudioOnDevice)
                    == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
                hfpAudioOnDevice = null;
            } else {
                activeDevices++;
            }
        }

@@ -736,24 +767,41 @@ public class BluetoothRouteManager extends StateMachine {
                        BluetoothProfile.HEARING_AID)) {
                if (device != null) {
                    hearingAidActiveDevice = device;
                    activeDevices++;
                    break;
                }
            }
        }

        // Return the active device reported by either HFP or hearing aid. If both are reporting
        // active devices, go with the most recent one as reported by the receiver.
        if (hfpAudioOnDevice != null) {
            if (hearingAidActiveDevice != null) {
                Log.i(this, "Both HFP and hearing aid are reporting active devices. Going with"
                        + " the most recently reported active device: %s");
        if (bluetoothLeAudio != null) {
            for (BluetoothDevice device : bluetoothLeAudio.getActiveDevices()) {
                if (device != null) {
                    leAudioActiveDevice = device;
                    activeDevices++;
                    break;
                }
            }
        }

        // Return the active device reported by either HFP, hearing aid or le audio. If more than
        // one is reporting active devices, go with the most recent one as reported by the receiver.
        if (activeDevices > 1) {
            Log.i(this, "More than one profile reporting active devices. Going with the most"
                    + " recently reported active device: %s", mMostRecentlyReportedActiveDevice);
            return mMostRecentlyReportedActiveDevice;
        }
            return hfpAudioOnDevice;

        if (leAudioActiveDevice != null) {
            return leAudioActiveDevice;
        }

        if (hearingAidActiveDevice != null) {
            return hearingAidActiveDevice;
        }

        return hfpAudioOnDevice;
    }

    /**
     * Check if in-band ringing is currently enabled. In-band ringing could be disabled during an
     * active connection.
@@ -847,10 +895,12 @@ public class BluetoothRouteManager extends StateMachine {
    }

    @VisibleForTesting
    public void setActiveDeviceCacheForTesting(BluetoothDevice device, boolean isHearingAid) {
        if (isHearingAid) {
    public void setActiveDeviceCacheForTesting(BluetoothDevice device, int deviceType) {
        if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) {
          mLeAudioActiveDeviceCache = device;
        } else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID) {
            mHearingAidActiveDeviceCache = device;
        } else {
        } else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEADSET) {
            mHfpActiveDeviceCache = device;
        }
    }
+61 −12

File changed.

Preview size limit exceeded, changes collapsed.

Loading