From 174c4975881a1c2929898baa016f323bf28f5b24 Mon Sep 17 00:00:00 2001 From: Eric Laurent Date: Fri, 13 Oct 2023 18:37:23 +0200 Subject: 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 Change-Id: I67bbba2ba941c97138a068d640079b17650e3d86 --- .../java/android/media/AudioDeviceAttributes.java | 26 ++- .../com/android/server/audio/AdiDeviceState.java | 3 +- .../android/server/audio/AudioDeviceBroker.java | 18 +- .../android/server/audio/AudioDeviceInventory.java | 52 ++++- .../com/android/server/audio/AudioService.java | 216 ++++++++++++++++++--- 5 files changed, 271 insertions(+), 44 deletions(-) diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java index af3c295b8d6c..5a274353f68e 100644 --- a/media/java/android/media/AudioDeviceAttributes.java +++ b/media/java/android/media/AudioDeviceAttributes.java @@ -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 diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java index 292fc14ac6eb..51cb9505ed4f 100644 --- a/services/core/java/com/android/server/audio/AdiDeviceState.java +++ b/services/core/java/com/android/server/audio/AdiDeviceState.java @@ -165,7 +165,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 + " bt audio type: " + AudioManager.audioDeviceCategoryToString(mAudioDeviceCategory) + " enabled: " + mSAEnabled + " HT: " + mHasHeadTracker diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index eea3d3885b34..2336753c88f9 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1252,8 +1252,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( @@ -1262,8 +1262,8 @@ public class AudioDeviceBroker { } /*package*/ void registerStrategyNonDefaultDevicesDispatcher( - @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) { - mDeviceInventory.registerStrategyNonDefaultDevicesDispatcher(dispatcher); + @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher, boolean isPrivileged) { + mDeviceInventory.registerStrategyNonDefaultDevicesDispatcher(dispatcher, isPrivileged); } /*package*/ void unregisterStrategyNonDefaultDevicesDispatcher( @@ -1281,8 +1281,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( @@ -1290,6 +1290,11 @@ public class AudioDeviceBroker { mDeviceInventory.unregisterCapturePresetDevicesRoleDispatcher(dispatcher); } + /* package */ List anonymizeAudioDeviceAttributesListUnchecked( + List devices) { + return mAudioService.anonymizeAudioDeviceAttributesListUnchecked(devices); + } + /*package*/ void registerCommunicationDeviceDispatcher( @NonNull ICommunicationDeviceDispatcher dispatcher) { mCommDevDispatchers.register(dispatcher); @@ -2684,4 +2689,5 @@ public class AudioDeviceBroker { void clearDeviceInventory() { mDeviceInventory.clearDeviceInventory(); } + } diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index e08fdd65ad94..a1d2e1412777 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -120,6 +120,26 @@ public class AudioDeviceInventory { } } + /** + * 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(); + } + /** * Adds a new AdiDeviceState or updates the audio device cateogory of the matching * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list. @@ -992,8 +1012,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( @@ -1002,8 +1022,8 @@ public class AudioDeviceInventory { } /*package*/ void registerStrategyNonDefaultDevicesDispatcher( - @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) { - mNonDefDevDispatchers.register(dispatcher); + @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher, boolean isPrivileged) { + mNonDefDevDispatchers.register(dispatcher, isPrivileged); } /*package*/ void unregisterStrategyNonDefaultDevicesDispatcher( @@ -1084,8 +1104,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( @@ -1414,6 +1434,8 @@ public class AudioDeviceInventory { updateBluetoothPreferredModes_l(connect ? btDevice : null /*connectedDevice*/); if (!connect) { purgeDevicesRoles_l(); + } else { + addAudioDeviceInInventoryIfNeeded(attributes); } } mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record(); @@ -1702,6 +1724,7 @@ public class AudioDeviceInventory { setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/); updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/); + addAudioDeviceInInventoryIfNeeded(ada); } static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER, @@ -2006,9 +2029,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( @@ -2018,6 +2041,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, @@ -2128,6 +2152,7 @@ public class AudioDeviceInventory { sensorUuid)); mDeviceBroker.postAccessoryPlugMediaUnmute(device); setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false); + addAudioDeviceInInventoryIfNeeded(ada); } if (streamType == AudioSystem.STREAM_DEFAULT) { @@ -2462,6 +2487,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) { @@ -2475,6 +2503,9 @@ public class AudioDeviceInventory { final int nbDispatchers = mNonDefDevDispatchers.beginBroadcast(); for (int i = 0; i < nbDispatchers; i++) { try { + if (!((Boolean) mNonDefDevDispatchers.getBroadcastCookie(i))) { + devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices); + } mNonDefDevDispatchers.getBroadcastItem(i).dispatchNonDefDevicesChanged( strategy, devices); } catch (RemoteException e) { @@ -2488,6 +2519,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) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 3243385c3b18..9b03afbcc569 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -36,6 +36,7 @@ import static android.os.Process.INVALID_UID; import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE; import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE; + import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE; import static com.android.server.utils.EventLogger.Event.ALOGE; import static com.android.server.utils.EventLogger.Event.ALOGI; @@ -201,6 +202,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; +import com.android.media.audio.flags.Flags; import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -2881,7 +2883,7 @@ public class AudioService extends IAudioService.Stub // IPC methods /////////////////////////////////////////////////////////////////////////// /** - * @see AudioManager#setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes) + * @see AudioManager#setPreferredDevicesForStrategy(AudioProductStrategy, AudioDeviceAttributes) * @see AudioManager#setPreferredDevicesForStrategy(AudioProductStrategy, * List) */ @@ -2891,8 +2893,11 @@ public class AudioService extends IAudioService.Stub if (devices == null) { return AudioSystem.ERROR; } + + devices = retrieveBluetoothAddresses(devices); + final String logString = String.format( - "setPreferredDeviceForStrategy u/pid:%d/%d strat:%d dev:%s", + "setPreferredDevicesForStrategy u/pid:%d/%d strat:%d dev:%s", Binder.getCallingUid(), Binder.getCallingPid(), strategy, devices.stream().map(e -> e.toString()).collect(Collectors.joining(","))); sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG)); @@ -2948,7 +2953,7 @@ public class AudioService extends IAudioService.Stub status, strategy)); return new ArrayList(); } else { - return devices; + return anonymizeAudioDeviceAttributesList(devices); } } @@ -2963,6 +2968,9 @@ public class AudioService extends IAudioService.Stub @NonNull AudioDeviceAttributes device) { super.setDeviceAsNonDefaultForStrategy_enforcePermission(); Objects.requireNonNull(device); + + device = retrieveBluetoothAddress(device); + final String logString = String.format( "setDeviceAsNonDefaultForStrategy u/pid:%d/%d strat:%d dev:%s", Binder.getCallingUid(), Binder.getCallingPid(), strategy, device.toString()); @@ -2989,6 +2997,9 @@ public class AudioService extends IAudioService.Stub AudioDeviceAttributes device) { super.removeDeviceAsNonDefaultForStrategy_enforcePermission(); Objects.requireNonNull(device); + + device = retrieveBluetoothAddress(device); + final String logString = String.format( "removeDeviceAsNonDefaultForStrategy strat:%d dev:%s", strategy, device.toString()); sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG)); @@ -3023,7 +3034,7 @@ public class AudioService extends IAudioService.Stub status, strategy)); return new ArrayList(); } else { - return devices; + return anonymizeAudioDeviceAttributesList(devices); } } @@ -3036,7 +3047,8 @@ public class AudioService extends IAudioService.Stub return; } enforceModifyAudioRoutingPermission(); - mDeviceBroker.registerStrategyPreferredDevicesDispatcher(dispatcher); + mDeviceBroker.registerStrategyPreferredDevicesDispatcher( + dispatcher, isBluetoothPrividged()); } /** @see AudioManager#removeOnPreferredDevicesForStrategyChangedListener( @@ -3060,7 +3072,8 @@ public class AudioService extends IAudioService.Stub return; } enforceModifyAudioRoutingPermission(); - mDeviceBroker.registerStrategyNonDefaultDevicesDispatcher(dispatcher); + mDeviceBroker.registerStrategyNonDefaultDevicesDispatcher( + dispatcher, isBluetoothPrividged()); } /** @see AudioManager#removeOnNonDefaultDevicesForStrategyChangedListener( @@ -3076,7 +3089,7 @@ public class AudioService extends IAudioService.Stub } /** - * @see AudioManager#setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes) + * @see AudioManager#setPreferredDevicesForCapturePreset(int, AudioDeviceAttributes) */ public int setPreferredDevicesForCapturePreset( int capturePreset, List devices) { @@ -3095,6 +3108,8 @@ public class AudioService extends IAudioService.Stub return AudioSystem.ERROR; } + devices = retrieveBluetoothAddresses(devices); + final int status = mDeviceBroker.setPreferredDevicesForCapturePresetSync( capturePreset, devices); if (status != AudioSystem.SUCCESS) { @@ -3141,7 +3156,7 @@ public class AudioService extends IAudioService.Stub status, capturePreset)); return new ArrayList(); } else { - return devices; + return anonymizeAudioDeviceAttributesList(devices); } } @@ -3155,7 +3170,8 @@ public class AudioService extends IAudioService.Stub return; } enforceModifyAudioRoutingPermission(); - mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(dispatcher); + mDeviceBroker.registerCapturePresetDevicesRoleDispatcher( + dispatcher, isBluetoothPrividged()); } /** @@ -3175,7 +3191,9 @@ public class AudioService extends IAudioService.Stub public @NonNull ArrayList getDevicesForAttributes( @NonNull AudioAttributes attributes) { enforceQueryStateOrModifyRoutingPermission(); - return getDevicesForAttributesInt(attributes, false /* forVolume */); + + return new ArrayList(anonymizeAudioDeviceAttributesList( + getDevicesForAttributesInt(attributes, false /* forVolume */))); } /** @see AudioManager#getAudioDevicesForAttributes(AudioAttributes) @@ -3185,7 +3203,8 @@ public class AudioService extends IAudioService.Stub */ public @NonNull ArrayList getDevicesForAttributesUnprotected( @NonNull AudioAttributes attributes) { - return getDevicesForAttributesInt(attributes, false /* forVolume */); + return new ArrayList(anonymizeAudioDeviceAttributesList( + getDevicesForAttributesInt(attributes, false /* forVolume */))); } /** @@ -7390,6 +7409,8 @@ public class AudioService extends IAudioService.Stub Objects.requireNonNull(device); AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior); + device = retrieveBluetoothAddress(device); + sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:" + AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:" + device.getAddress() + " behavior:" @@ -7473,6 +7494,8 @@ public class AudioService extends IAudioService.Stub // verify parameters Objects.requireNonNull(device); + device = retrieveBluetoothAddress(device); + return getDeviceVolumeBehaviorInt(device); } @@ -7547,9 +7570,12 @@ public class AudioService extends IAudioService.Stub /** * see AudioManager.setWiredDeviceConnectionState() */ - public void setWiredDeviceConnectionState(AudioDeviceAttributes attributes, + public void setWiredDeviceConnectionState(@NonNull AudioDeviceAttributes attributes, @ConnectionState int state, String caller) { super.setWiredDeviceConnectionState_enforcePermission(); + Objects.requireNonNull(attributes); + + attributes = retrieveBluetoothAddress(attributes); if (state != CONNECTION_STATE_CONNECTED && state != CONNECTION_STATE_DISCONNECTED) { @@ -7590,6 +7616,9 @@ public class AudioService extends IAudioService.Stub boolean connected) { Objects.requireNonNull(device); enforceModifyAudioRoutingPermission(); + + device = retrieveBluetoothAddress(device); + mDeviceBroker.setTestDeviceConnectionState(device, connected ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED); // simulate a routing update from native @@ -10422,6 +10451,103 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect); } + private boolean isBluetoothPrividged() { + if (!Flags.bluetoothMacAddressAnonymization()) { + return true; + } + return PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( + android.Manifest.permission.BLUETOOTH_CONNECT) + || Binder.getCallingUid() == Process.SYSTEM_UID; + } + + List retrieveBluetoothAddresses(List devices) { + if (isBluetoothPrividged()) { + return devices; + } + + List checkedDevices = new ArrayList(); + 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; + } + + /** + * 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 anonymizeAudioDeviceAttributesList( + List devices) { + if (isBluetoothPrividged()) { + return devices; + } + return anonymizeAudioDeviceAttributesListUnchecked(devices); + } + + /* package */ List anonymizeAudioDeviceAttributesListUnchecked( + List devices) { + List anonymizedDevices = new ArrayList(); + 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); + } + //========================================================================================== // camera sound is forced if any of the resources corresponding to one active SIM @@ -10469,13 +10595,16 @@ public class AudioService extends IAudioService.Stub Objects.requireNonNull(usages); Objects.requireNonNull(device); enforceModifyAudioRoutingPermission(); + + final AudioDeviceAttributes ada = retrieveBluetoothAddress(device); + if (timeOutMs <= 0 || usages.length == 0) { throw new IllegalArgumentException("Invalid timeOutMs/usagesToMute"); } Log.i(TAG, "muteAwaitConnection dev:" + device + " timeOutMs:" + timeOutMs + " usages:" + Arrays.toString(usages)); - if (mDeviceBroker.isDeviceConnected(device)) { + if (mDeviceBroker.isDeviceConnected(ada)) { // not throwing an exception as there could be a race between a connection (server-side, // notification of connection in flight) and a mute operation (client-side) Log.i(TAG, "muteAwaitConnection ignored, device (" + device + ") already connected"); @@ -10487,12 +10616,19 @@ public class AudioService extends IAudioService.Stub + mMutingExpectedDevice); throw new IllegalStateException("muteAwaitConnection already in progress"); } - mMutingExpectedDevice = device; + mMutingExpectedDevice = ada; mMutedUsagesAwaitingConnection = usages; - mPlaybackMonitor.muteAwaitConnection(usages, device, timeOutMs); + mPlaybackMonitor.muteAwaitConnection(usages, ada, timeOutMs); } - dispatchMuteAwaitConnection(cb -> { try { - cb.dispatchOnMutedUntilConnection(device, usages); } catch (RemoteException e) { } }); + dispatchMuteAwaitConnection((cb, isPrivileged) -> { + try { + AudioDeviceAttributes dev = ada; + if (!isPrivileged) { + dev = anonymizeAudioDeviceAttributesUnchecked(ada); + } + cb.dispatchOnMutedUntilConnection(dev, usages); + } catch (RemoteException e) { } + }); } @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) @@ -10501,7 +10637,7 @@ public class AudioService extends IAudioService.Stub super.getMutingExpectedDevice_enforcePermission(); synchronized (mMuteAwaitConnectionLock) { - return mMutingExpectedDevice; + return anonymizeAudioDeviceAttributes(mMutingExpectedDevice); } } @@ -10510,6 +10646,9 @@ public class AudioService extends IAudioService.Stub public void cancelMuteAwaitConnection(@NonNull AudioDeviceAttributes device) { Objects.requireNonNull(device); enforceModifyAudioRoutingPermission(); + + final AudioDeviceAttributes ada = retrieveBluetoothAddress(device); + Log.i(TAG, "cancelMuteAwaitConnection for device:" + device); final int[] mutedUsages; synchronized (mMuteAwaitConnectionLock) { @@ -10519,7 +10658,7 @@ public class AudioService extends IAudioService.Stub Log.i(TAG, "cancelMuteAwaitConnection ignored, no expected device"); return; } - if (!device.equalTypeAddress(mMutingExpectedDevice)) { + if (!ada.equalTypeAddress(mMutingExpectedDevice)) { Log.e(TAG, "cancelMuteAwaitConnection ignored, got " + device + "] but expected device is" + mMutingExpectedDevice); throw new IllegalStateException("cancelMuteAwaitConnection for wrong device"); @@ -10529,8 +10668,14 @@ public class AudioService extends IAudioService.Stub mMutedUsagesAwaitingConnection = null; mPlaybackMonitor.cancelMuteAwaitConnection("cancelMuteAwaitConnection dev:" + device); } - dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent( - AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, device, mutedUsages); + dispatchMuteAwaitConnection((cb, isPrivileged) -> { + try { + AudioDeviceAttributes dev = ada; + if (!isPrivileged) { + dev = anonymizeAudioDeviceAttributesUnchecked(ada); + } + cb.dispatchOnUnmutedEvent( + AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, dev, mutedUsages); } catch (RemoteException e) { } }); } @@ -10544,7 +10689,7 @@ public class AudioService extends IAudioService.Stub super.registerMuteAwaitConnectionDispatcher_enforcePermission(); if (register) { - mMuteAwaitConnectionDispatchers.register(cb); + mMuteAwaitConnectionDispatchers.register(cb, isBluetoothPrividged()); } else { mMuteAwaitConnectionDispatchers.unregister(cb); } @@ -10568,8 +10713,14 @@ public class AudioService extends IAudioService.Stub mPlaybackMonitor.cancelMuteAwaitConnection( "checkMuteAwaitConnection device " + device + " connected, unmuting"); } - dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent( - AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION, device, mutedUsages); + dispatchMuteAwaitConnection((cb, isPrivileged) -> { + try { + AudioDeviceAttributes ada = device; + if (!isPrivileged) { + ada = anonymizeAudioDeviceAttributesUnchecked(device); + } + cb.dispatchOnUnmutedEvent(AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION, + ada, mutedUsages); } catch (RemoteException e) { } }); } @@ -10589,7 +10740,8 @@ public class AudioService extends IAudioService.Stub mMutingExpectedDevice = null; mMutedUsagesAwaitingConnection = null; } - dispatchMuteAwaitConnection(cb -> { try { + dispatchMuteAwaitConnection((cb, isPrivileged) -> { + try { cb.dispatchOnUnmutedEvent( AudioManager.MuteAwaitConnectionCallback.EVENT_TIMEOUT, timedOutDevice, mutedUsages); @@ -10597,13 +10749,14 @@ public class AudioService extends IAudioService.Stub } private void dispatchMuteAwaitConnection( - java.util.function.Consumer callback) { + java.util.function.BiConsumer callback) { final int nbDispatchers = mMuteAwaitConnectionDispatchers.beginBroadcast(); // lazy initialization as errors unlikely ArrayList errorList = null; for (int i = 0; i < nbDispatchers; i++) { try { - callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i)); + callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i), + (Boolean) mMuteAwaitConnectionDispatchers.getBroadcastCookie(i)); } catch (Exception e) { if (errorList == null) { errorList = new ArrayList<>(1); @@ -13243,6 +13396,9 @@ 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 @@ -13263,6 +13419,9 @@ 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()); @@ -13290,6 +13449,9 @@ 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()); -- cgit v1.2.3-59-g8ed1b