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

Commit 740c912b authored by Eric Laurent's avatar Eric Laurent
Browse files

AudioService: Add support for LE headset groups in device inventory

Bluetooth LE Headsets have one MAC address per earbud and can use either
address when connecting to the phone. This must be taken into account
when matching a headset with an IMU sensor as the
sensor UUID corresponds to only one of the addresses.

The Headset properties persisted in the AdiDeviceState must also be
consistent for the two MAC addresses given any headset can be connected
first to AudioService.

Bug: 294278455
Test: make
Change-Id: Ideedfdc12b0b1a650dea6dbb016a32a62bc2f0fa
parent 70d13a4b
Loading
Loading
Loading
Loading
+63 −13
Original line number Diff line number Diff line
@@ -70,7 +70,6 @@ import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;


@@ -172,7 +171,7 @@ public class AudioDeviceBroker {
            @NonNull AudioSystemAdapter audioSystem) {
        mContext = context;
        mAudioService = service;
        mBtHelper = new BtHelper(this);
        mBtHelper = new BtHelper(this, context);
        mDeviceInventory = new AudioDeviceInventory(this);
        mSystemServer = SystemServerAdapter.getDefaultAdapter(mContext);
        mAudioSystem = audioSystem;
@@ -188,7 +187,7 @@ public class AudioDeviceBroker {
                      @NonNull AudioSystemAdapter audioSystem) {
        mContext = context;
        mAudioService = service;
        mBtHelper = new BtHelper(this);
        mBtHelper = new BtHelper(this, context);
        mDeviceInventory = mockDeviceInventory;
        mSystemServer = mockSystemServer;
        mAudioSystem = audioSystem;
@@ -1392,6 +1391,10 @@ public class AudioDeviceBroker {
        return mAudioService.hasAudioFocusUsers();
    }

    /*package*/ void postInitSpatializerHeadTrackingSensors() {
        mAudioService.postInitSpatializerHeadTrackingSensors();
    }

    //---------------------------------------------------------------------
    // Message handling on behalf of helper classes.
    // Each of these methods posts a message to mBrokerHandler message queue.
@@ -1475,6 +1478,15 @@ public class AudioDeviceBroker {
        sendLMsgNoDelay(MSG_L_RECEIVED_BT_EVENT, SENDMSG_QUEUE, intent);
    }

    /*package*/ void postUpdateLeAudioGroupAddresses(int groupId) {
        sendIMsgNoDelay(
                MSG_I_UPDATE_LE_AUDIO_GROUP_ADDRESSES, SENDMSG_QUEUE, groupId);
    }

    /*package*/ void postSynchronizeLeDevicesInInventory(AdiDeviceState deviceState) {
        sendLMsgNoDelay(MSG_L_SYNCHRONIZE_LE_DEVICES_IN_INVENTORY, SENDMSG_QUEUE, deviceState);
    }

    /*package*/ static final class CommunicationDeviceInfo {
        final @NonNull IBinder mCb; // Identifies the requesting client for death handler
        final int mUid; // Requester UID
@@ -1604,6 +1616,14 @@ public class AudioDeviceBroker {
        }
    }

    /*package*/ int getLeAudioDeviceGroupId(BluetoothDevice device) {
        return mBtHelper.getLeAudioDeviceGroupId(device);
    }

    /*package*/ List<String> getLeAudioGroupAddresses(int groupId) {
        return mBtHelper.getLeAudioGroupAddresses(groupId);
    }

    /*package*/ void broadcastStickyIntentToCurrentProfileGroup(Intent intent) {
        mSystemServer.broadcastStickyIntentToCurrentProfileGroup(intent);
    }
@@ -1976,6 +1996,22 @@ public class AudioDeviceBroker {
                        onCheckCommunicationRouteClientState(msg.arg1, msg.arg2 == 1);
                    }
                } break;

                case MSG_I_UPDATE_LE_AUDIO_GROUP_ADDRESSES:
                    synchronized (mSetModeLock) {
                        synchronized (mDeviceStateLock) {
                            mDeviceInventory.onUpdateLeAudioGroupAddresses(msg.arg1);
                        }
                    } break;

                case MSG_L_SYNCHRONIZE_LE_DEVICES_IN_INVENTORY:
                    synchronized (mSetModeLock) {
                        synchronized (mDeviceStateLock) {
                            mDeviceInventory.onSynchronizeLeDevicesInInventory(
                                    (AdiDeviceState) msg.obj);
                        }
                    } break;

                default:
                    Log.wtf(TAG, "Invalid message " + msg.what);
            }
@@ -2058,6 +2094,10 @@ public class AudioDeviceBroker {
    private static final int MSG_L_RECEIVED_BT_EVENT = 55;

    private static final int MSG_CHECK_COMMUNICATION_ROUTE_CLIENT_STATE = 56;
    private static final int MSG_I_UPDATE_LE_AUDIO_GROUP_ADDRESSES = 57;
    private static final int MSG_L_SYNCHRONIZE_LE_DEVICES_IN_INVENTORY = 58;



    private static boolean isMessageHandledUnderWakelock(int msgId) {
        switch(msgId) {
@@ -2582,9 +2622,9 @@ public class AudioDeviceBroker {
        }
    }

    @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
    List<String> getDeviceAddresses(AudioDeviceAttributes device) {
        synchronized (mDeviceStateLock) {
            return mDeviceInventory.getDeviceSensorUuid(device);
            return mDeviceInventory.getDeviceAddresses(device);
        }
    }

@@ -2605,15 +2645,19 @@ public class AudioDeviceBroker {
     * in order to be mocked by a test a the same package
     * (see https://code.google.com/archive/p/mockito/issues/127)
     */
    public void persistAudioDeviceSettings() {
    public void postPersistAudioDeviceSettings() {
        sendMsg(MSG_PERSIST_AUDIO_DEVICE_SETTINGS, SENDMSG_REPLACE, /*delay*/ 1000);
    }

    void onPersistAudioDeviceSettings() {
        final String deviceSettings = mDeviceInventory.getDeviceSettings();
        Log.v(TAG, "saving AdiDeviceState: " + deviceSettings);
        final SettingsAdapter settings = mAudioService.getSettings();
        boolean res = settings.putSecureStringForUser(mAudioService.getContentResolver(),
        Log.v(TAG, "onPersistAudioDeviceSettings AdiDeviceState: " + deviceSettings);
        String currentSettings = readDeviceSettings();
        if (deviceSettings.equals(currentSettings)) {
            return;
        }
        final SettingsAdapter settingsAdapter = mAudioService.getSettings();
        boolean res = settingsAdapter.putSecureStringForUser(mAudioService.getContentResolver(),
                Settings.Secure.AUDIO_DEVICE_INVENTORY,
                deviceSettings, UserHandle.USER_CURRENT);
        if (!res) {
@@ -2621,11 +2665,17 @@ public class AudioDeviceBroker {
        }
    }

    void onReadAudioDeviceSettings() {
    private String readDeviceSettings() {
        final SettingsAdapter settingsAdapter = mAudioService.getSettings();
        final ContentResolver contentResolver = mAudioService.getContentResolver();
        String settings = settingsAdapter.getSecureStringForUser(contentResolver,
        return settingsAdapter.getSecureStringForUser(contentResolver,
                Settings.Secure.AUDIO_DEVICE_INVENTORY, UserHandle.USER_CURRENT);
    }

    void onReadAudioDeviceSettings() {
        final SettingsAdapter settingsAdapter = mAudioService.getSettings();
        final ContentResolver contentResolver = mAudioService.getContentResolver();
        String settings = readDeviceSettings();
        if (settings == null) {
            Log.i(TAG, "reading AdiDeviceState from legacy key"
                    + Settings.Secure.SPATIAL_AUDIO_ENABLED);
@@ -2688,8 +2738,8 @@ public class AudioDeviceBroker {
    }

    @Nullable
    AdiDeviceState findBtDeviceStateForAddress(String address, boolean isBle) {
        return mDeviceInventory.findBtDeviceStateForAddress(address, isBle);
    AdiDeviceState findBtDeviceStateForAddress(String address, int deviceType) {
        return mDeviceInventory.findBtDeviceStateForAddress(address, deviceType);
    }

    //------------------------------------------------
+179 −60

File changed.

Preview size limit exceeded, changes collapsed.

+8 −6
Original line number Diff line number Diff line
@@ -236,7 +236,6 @@ import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -11051,7 +11050,9 @@ public class AudioService extends IAudioService.Stub
        final String addr = Objects.requireNonNull(address);
        AdiDeviceState deviceState = mDeviceBroker.findBtDeviceStateForAddress(addr, isBle);
        AdiDeviceState deviceState = mDeviceBroker.findBtDeviceStateForAddress(addr,
                (isBle ? AudioSystem.DEVICE_OUT_BLE_HEADSET
                        : AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
        int internalType = !isBle ? DEVICE_OUT_BLUETOOTH_A2DP
                : ((btAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES)
@@ -11067,7 +11068,7 @@ public class AudioService extends IAudioService.Stub
        deviceState.setAudioDeviceCategory(btAudioDeviceCategory);
        mDeviceBroker.addOrUpdateBtAudioDeviceCategoryInInventory(deviceState);
        mDeviceBroker.persistAudioDeviceSettings();
        mDeviceBroker.postPersistAudioDeviceSettings();
        mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes());
        mSoundDoseHelper.setAudioDeviceCategory(addr, internalType,
@@ -11081,7 +11082,8 @@ public class AudioService extends IAudioService.Stub
        super.getBluetoothAudioDeviceCategory_enforcePermission();
        final AdiDeviceState deviceState = mDeviceBroker.findBtDeviceStateForAddress(
                Objects.requireNonNull(address), isBle);
                Objects.requireNonNull(address), (isBle ? AudioSystem.DEVICE_OUT_BLE_HEADSET
                        : AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
        if (deviceState == null) {
            return AUDIO_DEVICE_CATEGORY_UNKNOWN;
        }
@@ -13585,8 +13587,8 @@ public class AudioService extends IAudioService.Stub
        return activeAssistantUids;
    }
    UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
        return mDeviceBroker.getDeviceSensorUuid(device);
    List<String> getDeviceAddresses(AudioDeviceAttributes device) {
        return mDeviceBroker.getDeviceAddresses(device);
    }
    //======================
+59 −1
Original line number Diff line number Diff line
@@ -26,7 +26,9 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothLeAudioCodecStatus;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
@@ -43,6 +45,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.server.utils.EventLogger;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -57,9 +60,11 @@ public class BtHelper {
    private static final String TAG = "AS.BtHelper";

    private final @NonNull AudioDeviceBroker mDeviceBroker;
    private final @NonNull Context mContext;

    BtHelper(@NonNull AudioDeviceBroker broker) {
    BtHelper(@NonNull AudioDeviceBroker broker, Context context) {
        mDeviceBroker = broker;
        mContext = context;
    }

    // BluetoothHeadset API to control SCO connection
@@ -498,6 +503,32 @@ public class BtHelper {
        }
    }

    // BluetoothLeAudio callback used to update the list of addresses in the same group as a
    // connected LE Audio device
    MyLeAudioCallback mLeAudioCallback = null;

    class MyLeAudioCallback implements BluetoothLeAudio.Callback {
        @Override
        public void onCodecConfigChanged(int groupId,
                                  @NonNull BluetoothLeAudioCodecStatus status) {
            // Do nothing
        }

        @Override
        public void onGroupNodeAdded(@NonNull BluetoothDevice device, int groupId) {
            mDeviceBroker.postUpdateLeAudioGroupAddresses(groupId);
        }

        @Override
        public void onGroupNodeRemoved(@NonNull BluetoothDevice device, int groupId) {
            mDeviceBroker.postUpdateLeAudioGroupAddresses(groupId);
        }
        @Override
        public void onGroupStatusChanged(int groupId, int groupStatus) {
            mDeviceBroker.postUpdateLeAudioGroupAddresses(groupId);
        }
    }

    // @GuardedBy("mDeviceBroker.mSetModeLock")
    @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
    /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) {
@@ -519,6 +550,11 @@ public class BtHelper {
                mHearingAid = (BluetoothHearingAid) proxy;
                break;
            case BluetoothProfile.LE_AUDIO:
                if (mLeAudio == null) {
                    mLeAudioCallback = new MyLeAudioCallback();
                    ((BluetoothLeAudio) proxy).registerCallback(
                            mContext.getMainExecutor(), mLeAudioCallback);
                }
                mLeAudio = (BluetoothLeAudio) proxy;
                break;
            case BluetoothProfile.A2DP_SINK:
@@ -977,6 +1013,28 @@ public class BtHelper {
        return result;
    }

    /*package*/ int getLeAudioDeviceGroupId(BluetoothDevice device) {
        if (mLeAudio == null || device == null) {
            return BluetoothLeAudio.GROUP_ID_INVALID;
        }
        return mLeAudio.getGroupId(device);
    }

    /*package*/ List<String> getLeAudioGroupAddresses(int groupId) {
        List<String> addresses = new ArrayList<String>();
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if (adapter == null || mLeAudio == null) {
            return addresses;
        }
        List<BluetoothDevice> activeDevices = adapter.getActiveDevices(BluetoothProfile.LE_AUDIO);
        for (BluetoothDevice device : activeDevices) {
            if (device != null && mLeAudio.getGroupId(device) == groupId) {
                addresses.add(device.getAddress());
            }
        }
        return addresses;
    }

    /**
     * Returns the String equivalent of the btCodecType.
     *
+24 −16
Original line number Diff line number Diff line
@@ -564,7 +564,7 @@ public class SpatializerHelper {
        }
        if (updatedDevice != null) {
            onRoutingUpdated();
            mDeviceBroker.persistAudioDeviceSettings();
            mDeviceBroker.postPersistAudioDeviceSettings();
            logDeviceState(updatedDevice, "addCompatibleAudioDevice");
        }
    }
@@ -614,7 +614,7 @@ public class SpatializerHelper {
        if (deviceState != null && deviceState.isSAEnabled()) {
            deviceState.setSAEnabled(false);
            onRoutingUpdated();
            mDeviceBroker.persistAudioDeviceSettings();
            mDeviceBroker.postPersistAudioDeviceSettings();
            logDeviceState(deviceState, "removeCompatibleAudioDevice");
        }
    }
@@ -716,7 +716,7 @@ public class SpatializerHelper {
                            ada.getAddress());
            initSAState(deviceState);
            mDeviceBroker.addOrUpdateDeviceSAStateInInventory(deviceState);
            mDeviceBroker.persistAudioDeviceSettings();
            mDeviceBroker.postPersistAudioDeviceSettings();
            logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later.
        }
    }
@@ -1206,7 +1206,7 @@ public class SpatializerHelper {
        }
        Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada);
        deviceState.setHeadTrackerEnabled(enabled);
        mDeviceBroker.persistAudioDeviceSettings();
        mDeviceBroker.postPersistAudioDeviceSettings();
        logDeviceState(deviceState, "setHeadTrackerEnabled");

        // check current routing to see if it affects the headtracking mode
@@ -1248,7 +1248,7 @@ public class SpatializerHelper {
        if (deviceState != null) {
            if (!deviceState.hasHeadTracker()) {
                deviceState.setHasHeadTracker(true);
                mDeviceBroker.persistAudioDeviceSettings();
                mDeviceBroker.postPersistAudioDeviceSettings();
                logDeviceState(deviceState, "setHasHeadTracker");
            }
            return deviceState.isHeadTrackerEnabled();
@@ -1631,13 +1631,17 @@ public class SpatializerHelper {
            return headHandle;
        }
        final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
        UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(currentDevice);
        List<String> deviceAddresses = mAudioService.getDeviceAddresses(currentDevice);

        // We limit only to Sensor.TYPE_HEAD_TRACKER here to avoid confusion
        // with gaming sensors. (Note that Sensor.TYPE_ROTATION_VECTOR
        // and Sensor.TYPE_GAME_ROTATION_VECTOR are supported internally by
        // SensorPoseProvider).
        // Note: this is a dynamic sensor list right now.
        List<Sensor> sensors = mSensorManager.getDynamicSensorList(Sensor.TYPE_HEAD_TRACKER);
        for (String address : deviceAddresses) {
            UUID routingDeviceUuid = UuidUtils.uuidFromAudioDeviceAttributes(
                    new AudioDeviceAttributes(currentDevice.getInternalType(), address));
            for (Sensor sensor : sensors) {
                final UUID uuid = sensor.getUuid();
                if (uuid.equals(routingDeviceUuid)) {
@@ -1652,6 +1656,10 @@ public class SpatializerHelper {
                    // we do not break, perhaps we find a head tracker on device.
                }
            }
            if (headHandle != -1) {
                break;
            }
        }
        return headHandle;
    }

Loading