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

Commit 62e3fd3a authored by Eric Laurent's avatar Eric Laurent
Browse files

RESTRICT AUTOMERGE - Revert "AudioService: anonymize Bluetooth MAC addresses"

Revert submission 25262177-anon_bt_address_sc_v2_dev

Reason for revert:  b/329515274
Reverted changes: /q/submissionid:25262177-anon_bt_address_sc_v2_dev

Change-Id: Ie8e5f24aee8b09ac241f2b88052f957461be32ce
parent 787a07d9
Loading
Loading
Loading
Loading
+2 −22
Original line number Diff line number Diff line
@@ -64,7 +64,8 @@ public final class AudioDeviceAttributes implements Parcelable {
    /**
     * The unique address of the device. Some devices don't have addresses, only an empty string.
     */
    private @NonNull String mAddress;
    private final @NonNull String mAddress;

    /**
     * Is input or output device
     */
@@ -132,18 +133,6 @@ public final class AudioDeviceAttributes implements Parcelable {
        mNativeType = nativeType;
    }

    /**
     * @hide
     * Copy Constructor.
     * @param ada the copied AudioDeviceAttributes
     */
    public AudioDeviceAttributes(AudioDeviceAttributes ada) {
        mRole = ada.getRole();
        mType = ada.getType();
        mAddress = ada.getAddress();
        mNativeType = ada.getInternalType();
    }

    /**
     * @hide
     * Returns the role of a device
@@ -174,15 +163,6 @@ public final class AudioDeviceAttributes implements Parcelable {
        return mAddress;
    }

    /**
     * @hide
     * Sets the device address. Only used by audio service.
     */
    public void setAddress(@NonNull String address) {
        Objects.requireNonNull(address);
        mAddress = address;
    }

    /**
     * @hide
     * Returns the internal device type of a device
+1 −10
Original line number Diff line number Diff line
@@ -25,7 +25,6 @@ import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;

import java.util.Objects;

@@ -44,8 +43,6 @@ import java.util.Objects;
    private final int mInternalDeviceType;
    @NonNull
    private final String mDeviceAddress;
    /** Unique device id from internal device type and address. */
    private final Pair<Integer, String> mDeviceId;
    private boolean mSAEnabled;
    private boolean mHasHeadTracker = false;
    private boolean mHeadTrackerEnabled;
@@ -71,11 +68,6 @@ import java.util.Objects;
        }
        mDeviceAddress = isBluetoothDevice(mInternalDeviceType) ? Objects.requireNonNull(
                address) : "";
        mDeviceId = new Pair<>(mInternalDeviceType, mDeviceAddress);
    }

    public Pair<Integer, String> getDeviceId() {
        return mDeviceId;
    }


@@ -148,8 +140,7 @@ import java.util.Objects;

    @Override
    public String toString() {
        return "type: " + mDeviceType
                + " internal type: 0x" + Integer.toHexString(mInternalDeviceType)
        return "type: " + mDeviceType + "internal type: " + mInternalDeviceType
                + " addr: " + mDeviceAddress + " enabled: " + mSAEnabled
                + " HT: " + mHasHeadTracker + " HTenabled: " + mHeadTrackerEnabled;
    }
+4 −9
Original line number Diff line number Diff line
@@ -757,8 +757,8 @@ public class AudioDeviceBroker {
    }

    /*package*/ void registerStrategyPreferredDevicesDispatcher(
            @NonNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged) {
        mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher, isPrivileged);
            @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
        mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher);
    }

    /*package*/ void unregisterStrategyPreferredDevicesDispatcher(
@@ -776,8 +776,8 @@ public class AudioDeviceBroker {
    }

    /*package*/ void registerCapturePresetDevicesRoleDispatcher(
            @NonNull ICapturePresetDevicesRoleDispatcher dispatcher, boolean isPrivileged) {
        mDeviceInventory.registerCapturePresetDevicesRoleDispatcher(dispatcher, isPrivileged);
            @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
        mDeviceInventory.registerCapturePresetDevicesRoleDispatcher(dispatcher);
    }

    /*package*/ void unregisterCapturePresetDevicesRoleDispatcher(
@@ -785,11 +785,6 @@ public class AudioDeviceBroker {
        mDeviceInventory.unregisterCapturePresetDevicesRoleDispatcher(dispatcher);
    }

    /* package */ List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesListUnchecked(
            List<AudioDeviceAttributes> devices) {
        return mAudioService.anonymizeAudioDeviceAttributesListUnchecked(devices);
    }

    /*package*/ void registerCommunicationDeviceDispatcher(
            @NonNull ICommunicationDeviceDispatcher dispatcher) {
        mCommDevDispatchers.register(dispatcher);
+23 −69
Original line number Diff line number Diff line
@@ -42,7 +42,6 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
@@ -50,9 +49,7 @@ import com.android.internal.annotations.VisibleForTesting;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
@@ -77,58 +74,31 @@ public class AudioDeviceInventory {

    private final Object mDeviceInventoryLock = new Object();
    @GuardedBy("mDeviceInventoryLock")
    private final HashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory = new HashMap<>();
    private final ArrayList<AdiDeviceState> mDeviceInventory = new ArrayList<>(0);


    List<AdiDeviceState> getImmutableDeviceInventory() {
        synchronized (mDeviceInventoryLock) {
            return new ArrayList<AdiDeviceState>(mDeviceInventory.values());
            return List.copyOf(mDeviceInventory);
        }
    }

    void addDeviceStateToInventory(AdiDeviceState deviceState) {
        synchronized (mDeviceInventoryLock) {
            mDeviceInventory.put(deviceState.getDeviceId(), deviceState);
        }
    }

    /**
     * Adds a new entry in mDeviceInventory if the AudioDeviceAttributes passed is an sink
     * Bluetooth device and no corresponding entry already exists.
     * @param ada the device to add if needed
     */
    void addAudioDeviceInInventoryIfNeeded(AudioDeviceAttributes ada) {
        if (!AudioSystem.isBluetoothOutDevice(ada.getInternalType())) {
            return;
        }
        synchronized (mDeviceInventoryLock) {
            if (findDeviceStateForAudioDeviceAttributes(ada, ada.getType()) != null) {
                return;
            }
            AdiDeviceState ads = new AdiDeviceState(
                    ada.getType(), ada.getInternalType(), ada.getAddress());
            mDeviceInventory.put(ads.getDeviceId(), ads);
            mDeviceInventory.add(deviceState);
        }
        mDeviceBroker.persistAudioDeviceSettings();
    }

    /**
     * Finds the device state that matches the passed {@link AudioDeviceAttributes} and device
     * type. Note: currently this method only returns a valid device for A2DP and BLE devices.
     *
     * @param ada attributes of device to match
     * @param canonicalDeviceType external device type to match
     * @return the found {@link AdiDeviceState} matching a cached A2DP or BLE device or
     *         {@code null} otherwise.
     */
    AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada,
            int canonicalDeviceType) {
        final boolean isWireless = isBluetoothDevice(ada.getInternalType());

        synchronized (mDeviceInventoryLock) {
            for (AdiDeviceState deviceState : mDeviceInventory.values()) {
                if (deviceState.getDeviceType() == canonicalDeviceType
            for (AdiDeviceState deviceSetting : mDeviceInventory) {
                if (deviceSetting.getDeviceType() == canonicalDeviceType
                        && (!isWireless || ada.getAddress().equals(
                        deviceState.getDeviceAddress()))) {
                    return deviceState;
                        deviceSetting.getDeviceAddress()))) {
                    return deviceSetting;
                }
            }
        }
@@ -332,7 +302,7 @@ public class AudioDeviceInventory {
                    + " devices:" + devices); });
        pw.println("\ndevices:\n");
        synchronized (mDeviceInventoryLock) {
            for (AdiDeviceState device : mDeviceInventory.values()) {
            for (AdiDeviceState device : mDeviceInventory) {
                pw.println("\t" + device + "\n");
            }
        }
@@ -761,8 +731,8 @@ public class AudioDeviceInventory {
    }

    /*package*/ void registerStrategyPreferredDevicesDispatcher(
            @NonNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged) {
        mPrefDevDispatchers.register(dispatcher, isPrivileged);
            @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
        mPrefDevDispatchers.register(dispatcher);
    }

    /*package*/ void unregisterStrategyPreferredDevicesDispatcher(
@@ -796,8 +766,8 @@ public class AudioDeviceInventory {
    }

    /*package*/ void registerCapturePresetDevicesRoleDispatcher(
            @NonNull ICapturePresetDevicesRoleDispatcher dispatcher, boolean isPrivileged) {
        mDevRoleCapturePresetDispatchers.register(dispatcher, isPrivileged);
            @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
        mDevRoleCapturePresetDispatchers.register(dispatcher);
    }

    /*package*/ void unregisterCapturePresetDevicesRoleDispatcher(
@@ -852,10 +822,6 @@ public class AudioDeviceInventory {
                mConnectedDevices.put(deviceKey, new DeviceInfo(
                        device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
                mDeviceBroker.postAccessoryPlugMediaUnmute(device);
                if (AudioSystem.isBluetoothScoDevice(device)) {
                    addAudioDeviceInInventoryIfNeeded(new AudioDeviceAttributes(
                            device, address));
                }
                mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
                return true;
            } else if (!connect && isConnected) {
@@ -1074,11 +1040,8 @@ public class AudioDeviceInventory {

        mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
        setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/);
        addAudioDeviceInInventoryIfNeeded(new AudioDeviceAttributes(
                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
    }


    @GuardedBy("mDevicesLock")
    private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
        MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "a2dp." + address)
@@ -1181,8 +1144,6 @@ public class AudioDeviceInventory {
        mDeviceBroker.postApplyVolumeOnDevice(streamType,
                AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
        setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/);
        addAudioDeviceInInventoryIfNeeded(new AudioDeviceAttributes(
                AudioSystem.DEVICE_OUT_HEARING_AID, address));
        new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable")
                .set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
                .set(MediaMetrics.Property.DEVICE,
@@ -1479,9 +1440,6 @@ public class AudioDeviceInventory {
        final int nbDispatchers = mPrefDevDispatchers.beginBroadcast();
        for (int i = 0; i < nbDispatchers; i++) {
            try {
                if (!((Boolean) mPrefDevDispatchers.getBroadcastCookie(i))) {
                    devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices);
                }
                mPrefDevDispatchers.getBroadcastItem(i).dispatchPrefDevicesChanged(
                        strategy, devices);
            } catch (RemoteException e) {
@@ -1495,9 +1453,6 @@ public class AudioDeviceInventory {
        final int nbDispatchers = mDevRoleCapturePresetDispatchers.beginBroadcast();
        for (int i = 0; i < nbDispatchers; ++i) {
            try {
                if (!((Boolean) mDevRoleCapturePresetDispatchers.getBroadcastCookie(i))) {
                    devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices);
                }
                mDevRoleCapturePresetDispatchers.getBroadcastItem(i).dispatchDevicesRoleChanged(
                        capturePreset, role, devices);
            } catch (RemoteException e) {
@@ -1510,21 +1465,20 @@ public class AudioDeviceInventory {
        int deviceCatalogSize = 0;
        synchronized (mDeviceInventoryLock) {
            deviceCatalogSize = mDeviceInventory.size();

        }
        final StringBuilder settingsBuilder = new StringBuilder(
                deviceCatalogSize * AdiDeviceState.getPeristedMaxSize());

            Iterator<AdiDeviceState> iterator = mDeviceInventory.values().iterator();
            if (iterator.hasNext()) {
                settingsBuilder.append(iterator.next().toPersistableString());
            }
            while (iterator.hasNext()) {
        synchronized (mDeviceInventoryLock) {
            for (int i = 0; i < mDeviceInventory.size(); i++) {
                settingsBuilder.append(mDeviceInventory.get(i).toPersistableString());
                if (i != mDeviceInventory.size() - 1) {
                    settingsBuilder.append(SETTING_DEVICE_SEPARATOR_CHAR);
                settingsBuilder.append(iterator.next().toPersistableString());
                }
            return settingsBuilder.toString();
            }
        }
        return settingsBuilder.toString();
    }

    /*package*/ void setDeviceSettings(String settings) {
        clearDeviceInventory();
+7 −143
Original line number Diff line number Diff line
@@ -2408,11 +2408,8 @@ public class AudioService extends IAudioService.Stub
            return AudioSystem.ERROR;
        }
        enforceModifyAudioRoutingPermission();

        devices = retrieveBluetoothAddresses(devices);

        final String logString = String.format(
                "setPreferredDevicesForStrategy u/pid:%d/%d strat:%d dev:%s",
                "setPreferredDeviceForStrategy u/pid:%d/%d strat:%d dev:%s",
                Binder.getCallingUid(), Binder.getCallingPid(), strategy,
                devices.stream().map(e -> e.toString()).collect(Collectors.joining(",")));
        sDeviceLogger.log(new AudioEventLogger.StringEvent(logString).printLog(TAG));
@@ -2460,7 +2457,7 @@ public class AudioService extends IAudioService.Stub
                    status, strategy));
            return new ArrayList<AudioDeviceAttributes>();
        } else {
            return anonymizeAudioDeviceAttributesList(devices);
            return devices;
        }
    }

@@ -2473,8 +2470,7 @@ public class AudioService extends IAudioService.Stub
            return;
        }
        enforceModifyAudioRoutingPermission();
        mDeviceBroker.registerStrategyPreferredDevicesDispatcher(
                dispatcher, isBluetoothPrividged());
        mDeviceBroker.registerStrategyPreferredDevicesDispatcher(dispatcher);
    }

    /** @see AudioManager#removeOnPreferredDevicesForStrategyChangedListener(
@@ -2490,7 +2486,7 @@ public class AudioService extends IAudioService.Stub
    }

    /**
     * @see AudioManager#setPreferredDevicesForCapturePreset(int, AudioDeviceAttributes)
     * @see AudioManager#setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes)
     */
    public int setPreferredDevicesForCapturePreset(
            int capturePreset, List<AudioDeviceAttributes> devices) {
@@ -2509,8 +2505,6 @@ public class AudioService extends IAudioService.Stub
            return AudioSystem.ERROR;
        }

        devices = retrieveBluetoothAddresses(devices);

        final int status = mDeviceBroker.setPreferredDevicesForCapturePresetSync(
                capturePreset, devices);
        if (status != AudioSystem.SUCCESS) {
@@ -2549,7 +2543,7 @@ public class AudioService extends IAudioService.Stub
                    status, capturePreset));
            return new ArrayList<AudioDeviceAttributes>();
        } else {
            return anonymizeAudioDeviceAttributesList(devices);
            return devices;
        }
    }

@@ -2563,8 +2557,7 @@ public class AudioService extends IAudioService.Stub
            return;
        }
        enforceModifyAudioRoutingPermission();
        mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(
                dispatcher, isBluetoothPrividged());
        mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(dispatcher);
    }

    /**
@@ -2584,8 +2577,7 @@ public class AudioService extends IAudioService.Stub
    public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributes(
            @NonNull AudioAttributes attributes) {
        enforceQueryStateOrModifyRoutingPermission();
        return new ArrayList<AudioDeviceAttributes>(anonymizeAudioDeviceAttributesList(
                getDevicesForAttributesInt(attributes)));
        return getDevicesForAttributesInt(attributes);
    }

    /**
@@ -6166,9 +6158,6 @@ public class AudioService extends IAudioService.Stub
        // verify arguments
        Objects.requireNonNull(device);
        AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior);

        device = retrieveBluetoothAddress(device);

        if (pkgName == null) {
            pkgName = "";
        }
@@ -6221,8 +6210,6 @@ public class AudioService extends IAudioService.Stub
        // verify permissions
        enforceQueryStateOrModifyRoutingPermission();

        device = retrieveBluetoothAddress(device);

        // translate Java device type to native device type (for the devices masks for full / fixed)
        final int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
                device.getType());
@@ -6271,9 +6258,6 @@ public class AudioService extends IAudioService.Stub
            @ConnectionState int state, String address, String name,
            String caller) {
        enforceModifyAudioRoutingPermission();

        address = retrieveBluetoothAddress(type, address);

        if (state != CONNECTION_STATE_CONNECTED
                && state != CONNECTION_STATE_DISCONNECTED) {
            throw new IllegalArgumentException("Invalid state " + state);
@@ -8546,117 +8530,6 @@ public class AudioService extends IAudioService.Stub
                /*arg1*/ 0, /*arg2*/ 0, TAG, /*delay*/ 0);
    }

    private boolean isBluetoothPrividged() {
        return PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
                android.Manifest.permission.BLUETOOTH_CONNECT)
                || Binder.getCallingUid() == Process.SYSTEM_UID;
    }

    List<AudioDeviceAttributes> retrieveBluetoothAddresses(List<AudioDeviceAttributes> devices) {
        if (isBluetoothPrividged()) {
            return devices;
        }

        List<AudioDeviceAttributes> checkedDevices = new ArrayList<AudioDeviceAttributes>();
        for (AudioDeviceAttributes ada : devices) {
            if (ada == null) {
                continue;
            }
            checkedDevices.add(retrieveBluetoothAddressUncheked(ada));
        }
        return checkedDevices;
    }

    AudioDeviceAttributes retrieveBluetoothAddress(@NonNull AudioDeviceAttributes ada) {
        if (isBluetoothPrividged()) {
            return ada;
        }
        return retrieveBluetoothAddressUncheked(ada);
    }

    AudioDeviceAttributes retrieveBluetoothAddressUncheked(@NonNull AudioDeviceAttributes ada) {
        Objects.requireNonNull(ada);
        if (AudioSystem.isBluetoothDevice(ada.getInternalType())) {
            String anonymizedAddress = anonymizeBluetoothAddress(ada.getAddress());
            for (AdiDeviceState ads : mDeviceBroker.getImmutableDeviceInventory()) {
                if (!(AudioSystem.isBluetoothDevice(ads.getInternalDeviceType())
                        && (ada.getInternalType() == ads.getInternalDeviceType())
                        && anonymizedAddress.equals(anonymizeBluetoothAddress(
                                ads.getDeviceAddress())))) {
                    continue;
                }
                ada.setAddress(ads.getDeviceAddress());
                break;
            }
        }
        return ada;
    }

    private String retrieveBluetoothAddress(int type, String address) {
        if (isBluetoothPrividged() || !AudioSystem.isBluetoothDevice(type)
                || address == null) {
            return address;
        }
        String anonymizedAddress = anonymizeBluetoothAddress(address);
        for (AdiDeviceState ads : mDeviceBroker.getImmutableDeviceInventory()) {
            if (!(AudioSystem.isBluetoothDevice(ads.getInternalDeviceType())
                    && anonymizedAddress.equals(anonymizeBluetoothAddress(
                        ads.getDeviceAddress())))) {
                continue;
            }
            return ads.getDeviceAddress();
        }
        return address;
    }

    /**
     * Convert a Bluetooth MAC address to an anonymized one when exposed to a non privileged app
     * Must match the implementation of BluetoothUtils.toAnonymizedAddress()
     * @param address Mac address to be anonymized
     * @return anonymized mac address
     */
    static String anonymizeBluetoothAddress(String address) {
        if (address == null || address.length() != "AA:BB:CC:DD:EE:FF".length()) {
            return null;
        }
        return "XX:XX:XX:XX" + address.substring("XX:XX:XX:XX".length());
    }

    private List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesList(
                List<AudioDeviceAttributes> devices) {
        if (isBluetoothPrividged()) {
            return devices;
        }
        return anonymizeAudioDeviceAttributesListUnchecked(devices);
    }

    /* package */ List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesListUnchecked(
            List<AudioDeviceAttributes> devices) {
        List<AudioDeviceAttributes> anonymizedDevices = new ArrayList<AudioDeviceAttributes>();
        for (AudioDeviceAttributes ada : devices) {
            anonymizedDevices.add(anonymizeAudioDeviceAttributesUnchecked(ada));
        }
        return anonymizedDevices;
    }

    private AudioDeviceAttributes anonymizeAudioDeviceAttributesUnchecked(
            AudioDeviceAttributes ada) {
        if (!AudioSystem.isBluetoothDevice(ada.getInternalType())) {
            return ada;
        }
        AudioDeviceAttributes res = new AudioDeviceAttributes(ada);
        res.setAddress(anonymizeBluetoothAddress(ada.getAddress()));
        return res;
    }

    private AudioDeviceAttributes anonymizeAudioDeviceAttributes(AudioDeviceAttributes ada) {
        if (isBluetoothPrividged()) {
            return ada;
        }

        return anonymizeAudioDeviceAttributesUnchecked(ada);
    }

    //==========================================================================================
    private boolean readCameraSoundForced() {
        return SystemProperties.getBoolean("audio.camerasound.force", false) ||
@@ -10781,9 +10654,6 @@ public class AudioService extends IAudioService.Stub
            @NonNull AudioDeviceAttributes device, @IntRange(from = 0) long delayMillis) {
        Objects.requireNonNull(device, "device must not be null");
        enforceModifyAudioRoutingPermission();

        device = retrieveBluetoothAddress(device);

        final String getterKey = "additional_output_device_delay="
                + device.getInternalType() + "," + device.getAddress(); // "getter" key as an id.
        final String setterKey = getterKey + "," + delayMillis;     // append the delay for setter
@@ -10804,9 +10674,6 @@ public class AudioService extends IAudioService.Stub
    @IntRange(from = 0)
    public long getAdditionalOutputDeviceDelay(@NonNull AudioDeviceAttributes device) {
        Objects.requireNonNull(device, "device must not be null");

        device = retrieveBluetoothAddress(device);

        final String key = "additional_output_device_delay";
        final String reply = AudioSystem.getParameters(
                key + "=" + device.getInternalType() + "," + device.getAddress());
@@ -10834,9 +10701,6 @@ public class AudioService extends IAudioService.Stub
    @IntRange(from = 0)
    public long getMaxAdditionalOutputDeviceDelay(@NonNull AudioDeviceAttributes device) {
        Objects.requireNonNull(device, "device must not be null");

        device = retrieveBluetoothAddress(device);

        final String key = "max_additional_output_device_delay";
        final String reply = AudioSystem.getParameters(
                key + "=" + device.getInternalType() + "," + device.getAddress());