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

Commit a0354504 authored by Eric Laurent's avatar Eric Laurent Committed by Android Build Coastguard Worker
Browse files

AudioService: anonymize Bluetooth MAC addresses

Make sure APIs returning AudioDeviceAttributes from AudioService
anonymize the Bluetooth MAC addresses because those are considered
privacy sensitive.
Only expose the full MAC address to system and apps with BLUETOOTH_CONNECT
permission.

setters, getters and listeners for preferred device for strategy,
preferred device for capture preset and mute await connection are
modified:
- when entering AudioService, full MAC addresses are retrieved based on the
  known Bluetooth devices stored in AudioDeviceInventory.mDeviceInventory
- when exiting AudioService, MAC addresses are anonymized if the client
  app does not have BLUETOOTH_CONNECT permission or is not a system component

APIs based on AudioDeviceInfo do not need to be modified as the
AudioDeviceInfo MAC address is for the AudioPort cached in the app
process and AudioPorts are anonymized by the native audioserver before
being returned to client apps.

Bug: 285588444

Test: atest AudioManagerTest
Test: atest RoutingTest
Test: atest AudioCommunicationDeviceTest
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:4c1d23ed19823b2706866201eded0c556f83e018)
Merged-In: I67bbba2ba941c97138a068d640079b17650e3d86
Change-Id: I67bbba2ba941c97138a068d640079b17650e3d86
parent e9ad19e8
Loading
Loading
Loading
Loading
+25 −1
Original line number Diff line number Diff line
@@ -68,7 +68,7 @@ public final class AudioDeviceAttributes implements Parcelable {
    /**
     * The unique address of the device. Some devices don't have addresses, only an empty string.
     */
    private final @NonNull String mAddress;
    private @NonNull String mAddress;
    /**
     * The non-unique name of the device. Some devices don't have names, only an empty string.
     * Should not be used as a unique identifier for a device.
@@ -186,6 +186,21 @@ public final class AudioDeviceAttributes implements Parcelable {
        mAudioDescriptors = new ArrayList<>();
    }

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

    /**
     * @hide
     * Returns the role of a device
@@ -216,6 +231,15 @@ 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 name of the audio device, or an empty string for devices without one
+10 −1
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;

import java.util.Objects;

@@ -43,6 +44,8 @@ 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;
@@ -68,6 +71,11 @@ import java.util.Objects;
        }
        mDeviceAddress = isBluetoothDevice(mInternalDeviceType) ? Objects.requireNonNull(
                address) : "";
        mDeviceId = new Pair<>(mInternalDeviceType, mDeviceAddress);
    }

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


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

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

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

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

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

    /*package*/ void unregisterCapturePresetDevicesRoleDispatcher(
@@ -861,6 +861,11 @@ 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);
+76 −31
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ 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;
@@ -47,7 +48,9 @@ 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.Objects;
@@ -74,31 +77,58 @@ public class AudioDeviceInventory {

    private final Object mDeviceInventoryLock = new Object();
    @GuardedBy("mDeviceInventoryLock")
    private final ArrayList<AdiDeviceState> mDeviceInventory = new ArrayList<>(0);

    private final HashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory = new HashMap<>();

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

    void addDeviceStateToInventory(AdiDeviceState deviceState) {
        synchronized (mDeviceInventoryLock) {
            mDeviceInventory.add(deviceState);
            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);
        }
        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 deviceSetting : mDeviceInventory) {
                if (deviceSetting.getDeviceType() == canonicalDeviceType
            for (AdiDeviceState deviceState : mDeviceInventory.values()) {
                if (deviceState.getDeviceType() == canonicalDeviceType
                        && (!isWireless || ada.getAddress().equals(
                        deviceSetting.getDeviceAddress()))) {
                    return deviceSetting;
                        deviceState.getDeviceAddress()))) {
                    return deviceState;
                }
            }
        }
@@ -308,7 +338,7 @@ public class AudioDeviceInventory {
                    + " devices:" + devices); });
        pw.println("\ndevices:\n");
        synchronized (mDeviceInventoryLock) {
            for (AdiDeviceState device : mDeviceInventory) {
            for (AdiDeviceState device : mDeviceInventory.values()) {
                pw.println("\t" + device + "\n");
            }
        }
@@ -695,8 +725,8 @@ public class AudioDeviceInventory {
    }

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

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

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

    /*package*/ void unregisterCapturePresetDevicesRoleDispatcher(
@@ -809,6 +839,9 @@ public class AudioDeviceInventory {
                mConnectedDevices.put(deviceKey, new DeviceInfo(
                        device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
                mDeviceBroker.postAccessoryPlugMediaUnmute(device);
                if (AudioSystem.isBluetoothScoDevice(device)) {
                    addAudioDeviceInInventoryIfNeeded(attributes);
                }
                mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
                return true;
            } else if (!connect && isConnected) {
@@ -1038,8 +1071,9 @@ public class AudioDeviceInventory {
        mDeviceBroker.setBluetoothA2dpOnInt(true, true /*fromA2dp*/, eventSource);
        // at this point there could be another A2DP device already connected in APM, but it
        // doesn't matter as this new one will overwrite the previous one
        final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name),
        AudioDeviceAttributes ada = new AudioDeviceAttributes(
                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name);
        final int res = mAudioSystem.setDeviceConnectionState(ada,
                AudioSystem.DEVICE_STATE_AVAILABLE, a2dpCodec);

        // TODO: log in MediaMetrics once distinction between connection failure and
@@ -1061,8 +1095,7 @@ public class AudioDeviceInventory {
        // The convention for head tracking sensors associated with A2DP devices is to
        // use a UUID derived from the MAC address as follows:
        //   time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address
        UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(
                new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
        UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada);
        final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
                address, a2dpCodec, sensorUuid);
        final String diKey = di.getKey();
@@ -1073,8 +1106,10 @@ public class AudioDeviceInventory {

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


    @GuardedBy("mDevicesLock")
    private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
        MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "a2dp." + address)
@@ -1168,9 +1203,9 @@ public class AudioDeviceInventory {
        final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
                AudioSystem.DEVICE_OUT_HEARING_AID);
        mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType);

        mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
                AudioSystem.DEVICE_OUT_HEARING_AID, address, name),
        AudioDeviceAttributes ada = new AudioDeviceAttributes(
                AudioSystem.DEVICE_OUT_HEARING_AID, address, name);
        mAudioSystem.setDeviceConnectionState(ada,
                AudioSystem.DEVICE_STATE_AVAILABLE,
                AudioSystem.AUDIO_FORMAT_DEFAULT);
        mConnectedDevices.put(
@@ -1181,6 +1216,7 @@ public class AudioDeviceInventory {
        mDeviceBroker.postApplyVolumeOnDevice(streamType,
                AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
        setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/);
        addAudioDeviceInInventoryIfNeeded(ada);
        new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable")
                .set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
                .set(MediaMetrics.Property.DEVICE,
@@ -1217,13 +1253,15 @@ public class AudioDeviceInventory {
             */
            mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource);

            AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(device, address, name),
            AudioDeviceAttributes ada = new AudioDeviceAttributes(device, address, name);
            AudioSystem.setDeviceConnectionState(ada,
                    AudioSystem.DEVICE_STATE_AVAILABLE,
                    AudioSystem.AUDIO_FORMAT_DEFAULT);
            mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
                    new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
            mDeviceBroker.postAccessoryPlugMediaUnmute(device);
            setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
            addAudioDeviceInInventoryIfNeeded(ada);
        }

        if (streamType == AudioSystem.STREAM_DEFAULT) {
@@ -1524,6 +1562,9 @@ 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) {
@@ -1537,6 +1578,9 @@ 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) {
@@ -1561,20 +1605,21 @@ public class AudioDeviceInventory {
        int deviceCatalogSize = 0;
        synchronized (mDeviceInventoryLock) {
            deviceCatalogSize = mDeviceInventory.size();
        }

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

        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);
                }
            Iterator<AdiDeviceState> iterator = mDeviceInventory.values().iterator();
            if (iterator.hasNext()) {
                settingsBuilder.append(iterator.next().toPersistableString());
            }
            while (iterator.hasNext()) {
                settingsBuilder.append(SETTING_DEVICE_SEPARATOR_CHAR);
                settingsBuilder.append(iterator.next().toPersistableString());
            }
            return settingsBuilder.toString();
        }
    }

    /*package*/ void setDeviceSettings(String settings) {
        clearDeviceInventory();
+174 −23

File changed.

Preview size limit exceeded, changes collapsed.