diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-03-15 21:28:47 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-03-15 21:28:47 +0000 |
commit | 8af2c4d2fed0600c49af95c7e3b9c5462f709727 (patch) | |
tree | ba5c08e786ff79d51077ae7ef437098b8f2f434d | |
parent | 2744697978f00335f0674879c5f877c9c5c16ac6 (diff) | |
parent | a25615aac07de47d62810966af925da48402e836 (diff) | |
download | base-android13-security-release.tar.gz |
Merge cherrypicks of ['googleplex-android-review.googlesource.com/26599510', 'googleplex-android-review.googlesource.com/26599511'] into security-aosp-tm-release.android-security-13.0.0_r17android13-security-release
Change-Id: I1a04ce7b9e6d465f933deb45cecb72fcd0f1c48e
11 files changed, 419 insertions, 961 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e8ed8b8a85d4..96cc1f892551 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9389,13 +9389,6 @@ public final class Settings { public static final String SPATIAL_AUDIO_ENABLED = "spatial_audio_enabled"; /** - * Internal collection of audio device inventory items - * The device item stored are {@link com.android.server.audio.AdiDeviceState} - * @hide - */ - public static final String AUDIO_DEVICE_INVENTORY = "audio_device_inventory"; - - /** * Indicates whether notification display on the lock screen is enabled. * <p> * Type: int (0 for false, 1 for true) diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java index 5a274353f68e..af3c295b8d6c 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 @NonNull String mAddress; + private final @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. @@ -188,21 +188,6 @@ public final class AudioDeviceAttributes implements Parcelable { /** * @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 * @return the role */ @@ -233,15 +218,6 @@ public final class AudioDeviceAttributes implements Parcelable { /** * @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 * @return the device name */ diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 467464093e10..955bfcc902a4 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1208,9 +1208,6 @@ public class AudioSystem public static final Set<Integer> DEVICE_IN_ALL_SCO_SET; /** @hide */ public static final Set<Integer> DEVICE_IN_ALL_USB_SET; - /** @hide */ - public static final Set<Integer> DEVICE_IN_ALL_BLE_SET; - static { DEVICE_IN_ALL_SET = new HashSet<>(); DEVICE_IN_ALL_SET.add(DEVICE_IN_COMMUNICATION); @@ -1250,66 +1247,6 @@ public class AudioSystem DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_ACCESSORY); DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_DEVICE); DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_HEADSET); - - DEVICE_IN_ALL_BLE_SET = new HashSet<>(); - DEVICE_IN_ALL_BLE_SET.add(DEVICE_IN_BLE_HEADSET); - } - - /** @hide */ - public static boolean isBluetoothDevice(int deviceType) { - return isBluetoothA2dpOutDevice(deviceType) - || isBluetoothScoDevice(deviceType) - || isBluetoothLeDevice(deviceType); - } - - /** @hide */ - public static boolean isBluetoothOutDevice(int deviceType) { - return isBluetoothA2dpOutDevice(deviceType) - || isBluetoothScoOutDevice(deviceType) - || isBluetoothLeOutDevice(deviceType); - } - - /** @hide */ - public static boolean isBluetoothInDevice(int deviceType) { - return isBluetoothScoInDevice(deviceType) - || isBluetoothLeInDevice(deviceType); - } - - /** @hide */ - public static boolean isBluetoothA2dpOutDevice(int deviceType) { - return DEVICE_OUT_ALL_A2DP_SET.contains(deviceType); - } - - /** @hide */ - public static boolean isBluetoothScoOutDevice(int deviceType) { - return DEVICE_OUT_ALL_SCO_SET.contains(deviceType); - } - - /** @hide */ - public static boolean isBluetoothScoInDevice(int deviceType) { - return DEVICE_IN_ALL_SCO_SET.contains(deviceType); - } - - /** @hide */ - public static boolean isBluetoothScoDevice(int deviceType) { - return isBluetoothScoOutDevice(deviceType) - || isBluetoothScoInDevice(deviceType); - } - - /** @hide */ - public static boolean isBluetoothLeOutDevice(int deviceType) { - return DEVICE_OUT_ALL_BLE_SET.contains(deviceType); - } - - /** @hide */ - public static boolean isBluetoothLeInDevice(int deviceType) { - return DEVICE_IN_ALL_BLE_SET.contains(deviceType); - } - - /** @hide */ - public static boolean isBluetoothLeDevice(int deviceType) { - return isBluetoothLeOutDevice(deviceType) - || isBluetoothLeInDevice(deviceType); } /** @hide */ diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index da0689c6d177..cce515444c1f 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -687,7 +687,6 @@ public class SettingsBackupTest { Settings.Secure.ASSIST_SCREENSHOT_ENABLED, Settings.Secure.ASSIST_STRUCTURE_ENABLED, Settings.Secure.ATTENTIVE_TIMEOUT, - Settings.Secure.AUDIO_DEVICE_INVENTORY, // setting not controllable by user Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION, Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT, Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE, diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java deleted file mode 100644 index eab1eca90f6c..000000000000 --- a/services/core/java/com/android/server/audio/AdiDeviceState.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.audio; - -import static android.media.AudioSystem.DEVICE_NONE; -import static android.media.AudioSystem.isBluetoothDevice; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.media.AudioDeviceAttributes; -import android.media.AudioDeviceInfo; -import android.text.TextUtils; -import android.util.Log; -import android.util.Pair; - -import java.util.Objects; - -/** - * Class representing all devices that were previously or are currently connected. Data is - * persisted in {@link android.provider.Settings.Secure} - */ -/*package*/ final class AdiDeviceState { - private static final String TAG = "AS.AdiDeviceState"; - - private static final String SETTING_FIELD_SEPARATOR = ","; - - @AudioDeviceInfo.AudioDeviceType - private final int mDeviceType; - - 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; - - /** - * Constructor - * - * @param deviceType external audio device type - * @param internalDeviceType if not set pass {@link DEVICE_NONE}, in this case the - * default conversion of the external type will be used - * @param address must be non-null for wireless devices - * @throws NullPointerException if a null address is passed for a wireless device - */ - AdiDeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, - int internalDeviceType, - @Nullable String address) { - mDeviceType = deviceType; - if (internalDeviceType != DEVICE_NONE) { - mInternalDeviceType = internalDeviceType; - } else { - mInternalDeviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(deviceType); - - } - mDeviceAddress = isBluetoothDevice(mInternalDeviceType) ? Objects.requireNonNull( - address) : ""; - mDeviceId = new Pair<>(mInternalDeviceType, mDeviceAddress); - } - - public Pair<Integer, String> getDeviceId() { - return mDeviceId; - } - - - - @AudioDeviceInfo.AudioDeviceType - public int getDeviceType() { - return mDeviceType; - } - - public int getInternalDeviceType() { - return mInternalDeviceType; - } - - @NonNull - public String getDeviceAddress() { - return mDeviceAddress; - } - - public void setSAEnabled(boolean sAEnabled) { - mSAEnabled = sAEnabled; - } - - public boolean isSAEnabled() { - return mSAEnabled; - } - - public void setHeadTrackerEnabled(boolean headTrackerEnabled) { - mHeadTrackerEnabled = headTrackerEnabled; - } - - public boolean isHeadTrackerEnabled() { - return mHeadTrackerEnabled; - } - - public void setHasHeadTracker(boolean hasHeadTracker) { - mHasHeadTracker = hasHeadTracker; - } - - - public boolean hasHeadTracker() { - return mHasHeadTracker; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - // type check and cast - if (getClass() != obj.getClass()) { - return false; - } - final AdiDeviceState sads = (AdiDeviceState) obj; - return mDeviceType == sads.mDeviceType - && mInternalDeviceType == sads.mInternalDeviceType - && mDeviceAddress.equals(sads.mDeviceAddress) // NonNull - && mSAEnabled == sads.mSAEnabled - && mHasHeadTracker == sads.mHasHeadTracker - && mHeadTrackerEnabled == sads.mHeadTrackerEnabled; - } - - @Override - public int hashCode() { - return Objects.hash(mDeviceType, mInternalDeviceType, mDeviceAddress, mSAEnabled, - mHasHeadTracker, mHeadTrackerEnabled); - } - - @Override - public String toString() { - return "type: " + mDeviceType - + " internal type: 0x" + Integer.toHexString(mInternalDeviceType) - + " addr: " + mDeviceAddress + " enabled: " + mSAEnabled - + " HT: " + mHasHeadTracker + " HTenabled: " + mHeadTrackerEnabled; - } - - public String toPersistableString() { - return (new StringBuilder().append(mDeviceType) - .append(SETTING_FIELD_SEPARATOR).append(mDeviceAddress) - .append(SETTING_FIELD_SEPARATOR).append(mSAEnabled ? "1" : "0") - .append(SETTING_FIELD_SEPARATOR).append(mHasHeadTracker ? "1" : "0") - .append(SETTING_FIELD_SEPARATOR).append(mHeadTrackerEnabled ? "1" : "0") - .append(SETTING_FIELD_SEPARATOR).append(mInternalDeviceType) - .toString()); - } - - /** - * Gets the max size (including separators) when persisting the elements with - * {@link AdiDeviceState#toPersistableString()}. - */ - public static int getPeristedMaxSize() { - return 36; /* (mDeviceType)2 + (mDeviceAddresss)17 + (mInternalDeviceType)9 + (mSAEnabled)1 - + (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1 - + (SETTINGS_FIELD_SEPARATOR)5 */ - } - - @Nullable - public static AdiDeviceState fromPersistedString(@Nullable String persistedString) { - if (persistedString == null) { - return null; - } - if (persistedString.isEmpty()) { - return null; - } - String[] fields = TextUtils.split(persistedString, SETTING_FIELD_SEPARATOR); - // we may have 5 fields for the legacy AdiDeviceState and 6 containing the internal - // device type - if (fields.length != 5 && fields.length != 6) { - // expecting all fields, fewer may mean corruption, ignore those settings - return null; - } - try { - final int deviceType = Integer.parseInt(fields[0]); - int internalDeviceType = -1; - if (fields.length == 6) { - internalDeviceType = Integer.parseInt(fields[5]); - } - final AdiDeviceState deviceState = new AdiDeviceState(deviceType, - internalDeviceType, fields[1]); - deviceState.setHasHeadTracker(Integer.parseInt(fields[2]) == 1); - deviceState.setHasHeadTracker(Integer.parseInt(fields[3]) == 1); - deviceState.setHeadTrackerEnabled(Integer.parseInt(fields[4]) == 1); - return deviceState; - } catch (NumberFormatException e) { - Log.e(TAG, "unable to parse setting for AdiDeviceState: " + persistedString, e); - return null; - } - } - - public AudioDeviceAttributes getAudioDeviceAttributes() { - return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, - mDeviceType, mDeviceAddress); - } - -} diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 6ccdd827b45a..03dcc8d711d3 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -46,7 +46,6 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; -import android.provider.Settings; import android.text.TextUtils; import android.util.Log; import android.util.PrintWriterPrinter; @@ -64,11 +63,8 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; -/** - * @hide - * (non final for mocking/spying) - */ -public class AudioDeviceBroker { +/** @hide */ +/*package*/ final class AudioDeviceBroker { private static final String TAG = "AS.AudioDeviceBroker"; @@ -833,8 +829,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( @@ -852,8 +848,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( @@ -861,11 +857,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); @@ -1471,9 +1462,6 @@ public class AudioDeviceBroker { final int capturePreset = msg.arg1; mDeviceInventory.onSaveClearPreferredDevicesForCapturePreset(capturePreset); } break; - case MSG_PERSIST_AUDIO_DEVICE_SETTINGS: - onPersistAudioDeviceSettings(); - break; default: Log.wtf(TAG, "Invalid message " + msg.what); } @@ -1546,8 +1534,6 @@ public class AudioDeviceBroker { // process set volume for Le Audio, obj is BleVolumeInfo private static final int MSG_II_SET_LE_AUDIO_OUT_VOLUME = 46; - private static final int MSG_PERSIST_AUDIO_DEVICE_SETTINGS = 54; - private static boolean isMessageHandledUnderWakelock(int msgId) { switch(msgId) { case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: @@ -1933,95 +1919,4 @@ public class AudioDeviceBroker { return mDeviceInventory.getDeviceSensorUuid(device); } } - - /** - * post a message to persist the audio device settings. - * Message is delayed by 1s on purpose in case of successive changes in quick succession (at - * init time for instance) - * Note this method is made public to work around a Mockito bug where it needs to be public - * 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() { - sendMsg(MSG_PERSIST_AUDIO_DEVICE_SETTINGS, SENDMSG_REPLACE, /*delay*/ 1000); - } - - void onPersistAudioDeviceSettings() { - final String deviceSettings = mDeviceInventory.getDeviceSettings(); - Log.v(TAG, "saving audio device settings: " + deviceSettings); - final SettingsAdapter settings = mAudioService.getSettings(); - boolean res = settings.putSecureStringForUser(mAudioService.getContentResolver(), - Settings.Secure.AUDIO_DEVICE_INVENTORY, - deviceSettings, UserHandle.USER_CURRENT); - if (!res) { - Log.e(TAG, "error saving audio device settings: " + deviceSettings); - } - } - - void onReadAudioDeviceSettings() { - final SettingsAdapter settingsAdapter = mAudioService.getSettings(); - final ContentResolver contentResolver = mAudioService.getContentResolver(); - String settings = settingsAdapter.getSecureStringForUser(contentResolver, - Settings.Secure.AUDIO_DEVICE_INVENTORY, UserHandle.USER_CURRENT); - if (settings == null) { - Log.i(TAG, "reading spatial audio device settings from legacy key" - + Settings.Secure.SPATIAL_AUDIO_ENABLED); - // legacy string format for key SPATIAL_AUDIO_ENABLED has the same order of fields like - // the strings for key AUDIO_DEVICE_INVENTORY. This will ensure to construct valid - // device settings when calling {@link #setDeviceSettings()} - settings = settingsAdapter.getSecureStringForUser(contentResolver, - Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT); - if (settings == null) { - Log.i(TAG, "no spatial audio device settings stored with legacy key"); - } else if (!settings.equals("")) { - // Delete old key value and update the new key - if (!settingsAdapter.putSecureStringForUser(contentResolver, - Settings.Secure.SPATIAL_AUDIO_ENABLED, - /*value=*/"", - UserHandle.USER_CURRENT)) { - Log.w(TAG, "cannot erase the legacy audio device settings with key " - + Settings.Secure.SPATIAL_AUDIO_ENABLED); - } - if (!settingsAdapter.putSecureStringForUser(contentResolver, - Settings.Secure.AUDIO_DEVICE_INVENTORY, - settings, - UserHandle.USER_CURRENT)) { - Log.e(TAG, "error updating the new audio device settings with key " - + Settings.Secure.AUDIO_DEVICE_INVENTORY); - } - } - } - - if (settings != null && !settings.equals("")) { - setDeviceSettings(settings); - } - } - - void setDeviceSettings(String settings) { - mDeviceInventory.setDeviceSettings(settings); - } - - /** Test only method. */ - String getDeviceSettings() { - return mDeviceInventory.getDeviceSettings(); - } - - List<AdiDeviceState> getImmutableDeviceInventory() { - return mDeviceInventory.getImmutableDeviceInventory(); - } - - void addDeviceStateToInventory(AdiDeviceState deviceState) { - mDeviceInventory.addDeviceStateToInventory(deviceState); - } - - AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada, - int canonicalType) { - return mDeviceInventory.findDeviceStateForAudioDeviceAttributes(ada, canonicalType); - } - - //------------------------------------------------ - // for testing purposes only - 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 9524c54acc36..dbe4fb8c8795 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -15,8 +15,6 @@ */ package com.android.server.audio; -import static android.media.AudioSystem.isBluetoothDevice; - import android.annotation.NonNull; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; @@ -40,7 +38,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; @@ -48,9 +45,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.Objects; @@ -66,81 +61,12 @@ public class AudioDeviceInventory { private static final String TAG = "AS.AudioDeviceInventory"; - private static final String SETTING_DEVICE_SEPARATOR_CHAR = "|"; - private static final String SETTING_DEVICE_SEPARATOR = "\\|"; - // lock to synchronize all access to mConnectedDevices and mApmConnectedDevices private final Object mDevicesLock = new Object(); //Audio Analytics ids. private static final String mMetricsId = "audio.device."; - private final Object mDeviceInventoryLock = new Object(); - @GuardedBy("mDeviceInventoryLock") - private final HashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory = new HashMap<>(); - - List<AdiDeviceState> getImmutableDeviceInventory() { - synchronized (mDeviceInventoryLock) { - return new ArrayList<AdiDeviceState>(mDeviceInventory.values()); - } - } - - 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); - } - 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 - && (!isWireless || ada.getAddress().equals( - deviceState.getDeviceAddress()))) { - return deviceState; - } - } - } - return null; - } - - void clearDeviceInventory() { - synchronized (mDeviceInventoryLock) { - mDeviceInventory.clear(); - } - } - // List of connected devices // Key for map created from DeviceInfo.makeDeviceListKey() @GuardedBy("mDevicesLock") @@ -336,12 +262,6 @@ public class AudioDeviceInventory { mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> { pw.println(" " + prefix + "capturePreset:" + capturePreset + " devices:" + devices); }); - pw.println("\ndevices:\n"); - synchronized (mDeviceInventoryLock) { - for (AdiDeviceState device : mDeviceInventory.values()) { - pw.println("\t" + device + "\n"); - } - } } //------------------------------------------------------------ @@ -725,8 +645,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( @@ -760,8 +680,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( @@ -839,9 +759,6 @@ 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) { @@ -1071,9 +988,8 @@ 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 - AudioDeviceAttributes ada = new AudioDeviceAttributes( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name); - final int res = mAudioSystem.setDeviceConnectionState(ada, + final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name), AudioSystem.DEVICE_STATE_AVAILABLE, a2dpCodec); // TODO: log in MediaMetrics once distinction between connection failure and @@ -1095,7 +1011,8 @@ 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(ada); + UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes( + new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address)); final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name, address, a2dpCodec, sensorUuid); final String diKey = di.getKey(); @@ -1106,10 +1023,8 @@ 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) @@ -1203,9 +1118,9 @@ public class AudioDeviceInventory { final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType, AudioSystem.DEVICE_OUT_HEARING_AID); mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType); - AudioDeviceAttributes ada = new AudioDeviceAttributes( - AudioSystem.DEVICE_OUT_HEARING_AID, address, name); - mAudioSystem.setDeviceConnectionState(ada, + + mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( + AudioSystem.DEVICE_OUT_HEARING_AID, address, name), AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.put( @@ -1216,7 +1131,6 @@ 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, @@ -1253,15 +1167,13 @@ public class AudioDeviceInventory { */ mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource); - AudioDeviceAttributes ada = new AudioDeviceAttributes(device, address, name); - AudioSystem.setDeviceConnectionState(ada, + AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(device, address, name), 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) { @@ -1562,9 +1474,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) { @@ -1578,9 +1487,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) { @@ -1600,42 +1506,6 @@ public class AudioDeviceInventory { return di.mSensorUuid; } } - - /*package*/ String getDeviceSettings() { - 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()) { - settingsBuilder.append(SETTING_DEVICE_SEPARATOR_CHAR); - settingsBuilder.append(iterator.next().toPersistableString()); - } - return settingsBuilder.toString(); - } - } - - /*package*/ void setDeviceSettings(String settings) { - clearDeviceInventory(); - String[] devSettings = TextUtils.split(Objects.requireNonNull(settings), - SETTING_DEVICE_SEPARATOR); - // small list, not worth overhead of Arrays.stream(devSettings) - for (String setting : devSettings) { - AdiDeviceState devState = AdiDeviceState.fromPersistedString(setting); - // Note if the device is not compatible with spatialization mode or the device - // type is not canonical, it will be ignored in {@link SpatializerHelper}. - if (devState != null) { - addDeviceStateToInventory(devState); - } - } - } - //---------------------------------------------------------- // For tests only diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 58b07e385ecf..02648c4da76f 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -341,6 +341,7 @@ public class AudioService extends IAudioService.Stub private static final int MSG_DISPATCH_AUDIO_MODE = 40; private static final int MSG_ROUTING_UPDATED = 41; private static final int MSG_INIT_HEADTRACKING_SENSORS = 42; + private static final int MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS = 43; private static final int MSG_ADD_ASSISTANT_SERVICE_UID = 44; private static final int MSG_REMOVE_ASSISTANT_SERVICE_UID = 45; private static final int MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID = 46; @@ -952,8 +953,6 @@ public class AudioService extends IAudioService.Stub mPlatformType = AudioSystem.getPlatformType(context); - mDeviceBroker = new AudioDeviceBroker(mContext, this); - mIsSingleVolume = AudioSystem.isSingleVolume(context); mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); @@ -966,7 +965,7 @@ public class AudioService extends IAudioService.Stub mSfxHelper = new SoundEffectsHelper(mContext); - mSpatializerHelper = new SpatializerHelper(this, mAudioSystem, mDeviceBroker); + mSpatializerHelper = new SpatializerHelper(this, mAudioSystem); mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator(); @@ -1102,6 +1101,8 @@ public class AudioService extends IAudioService.Stub mUseFixedVolume = mContext.getResources().getBoolean( com.android.internal.R.bool.config_useFixedVolume); + mDeviceBroker = new AudioDeviceBroker(mContext, this); + mRecordMonitor = new RecordingActivityMonitor(mContext); mRecordMonitor.registerRecordingCallback(mVoiceRecordingActivityMonitor, true); @@ -2638,11 +2639,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)); @@ -2690,7 +2688,7 @@ public class AudioService extends IAudioService.Stub status, strategy)); return new ArrayList<AudioDeviceAttributes>(); } else { - return anonymizeAudioDeviceAttributesList(devices); + return devices; } } @@ -2703,8 +2701,7 @@ public class AudioService extends IAudioService.Stub return; } enforceModifyAudioRoutingPermission(); - mDeviceBroker.registerStrategyPreferredDevicesDispatcher( - dispatcher, isBluetoothPrividged()); + mDeviceBroker.registerStrategyPreferredDevicesDispatcher(dispatcher); } /** @see AudioManager#removeOnPreferredDevicesForStrategyChangedListener( @@ -2720,7 +2717,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) { @@ -2739,8 +2736,6 @@ public class AudioService extends IAudioService.Stub return AudioSystem.ERROR; } - devices = retrieveBluetoothAddresses(devices); - final int status = mDeviceBroker.setPreferredDevicesForCapturePresetSync( capturePreset, devices); if (status != AudioSystem.SUCCESS) { @@ -2779,7 +2774,7 @@ public class AudioService extends IAudioService.Stub status, capturePreset)); return new ArrayList<AudioDeviceAttributes>(); } else { - return anonymizeAudioDeviceAttributesList(devices); + return devices; } } @@ -2793,8 +2788,7 @@ public class AudioService extends IAudioService.Stub return; } enforceModifyAudioRoutingPermission(); - mDeviceBroker.registerCapturePresetDevicesRoleDispatcher( - dispatcher, isBluetoothPrividged()); + mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(dispatcher); } /** @@ -2814,9 +2808,7 @@ public class AudioService extends IAudioService.Stub public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributes( @NonNull AudioAttributes attributes) { enforceQueryStateOrModifyRoutingPermission(); - - return new ArrayList<AudioDeviceAttributes>(anonymizeAudioDeviceAttributesList( - getDevicesForAttributesInt(attributes, false /* forVolume */))); + return getDevicesForAttributesInt(attributes, false /* forVolume */); } /** @see AudioManager#getAudioDevicesForAttributes(AudioAttributes) @@ -2826,8 +2818,7 @@ public class AudioService extends IAudioService.Stub */ public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributesUnprotected( @NonNull AudioAttributes attributes) { - return new ArrayList<AudioDeviceAttributes>(anonymizeAudioDeviceAttributesList( - getDevicesForAttributesInt(attributes, false /* forVolume */))); + return getDevicesForAttributesInt(attributes, false /* forVolume */); } /** @@ -5934,10 +5925,6 @@ public class AudioService extends IAudioService.Stub } } - /*package*/ SettingsAdapter getSettings() { - return mSettings; - } - /////////////////////////////////////////////////////////////////////////// // Internal methods /////////////////////////////////////////////////////////////////////////// @@ -6649,9 +6636,6 @@ public class AudioService extends IAudioService.Stub // verify arguments Objects.requireNonNull(device); AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior); - - device = retrieveBluetoothAddress(device); - sVolumeLogger.log(new AudioEventLogger.StringEvent("setDeviceVolumeBehavior: dev:" + AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:" + device.getAddress() + " behavior:" @@ -6728,8 +6712,6 @@ public class AudioService extends IAudioService.Stub // verify permissions enforceQueryStateOrModifyRoutingPermission(); - device = retrieveBluetoothAddress(device); - return getDeviceVolumeBehaviorInt(device); } @@ -6806,9 +6788,6 @@ public class AudioService extends IAudioService.Stub public void setWiredDeviceConnectionState(AudioDeviceAttributes attributes, @ConnectionState int state, String caller) { enforceModifyAudioRoutingPermission(); - - attributes = retrieveBluetoothAddress(attributes); - if (state != CONNECTION_STATE_CONNECTED && state != CONNECTION_STATE_DISCONNECTED) { throw new IllegalArgumentException("Invalid state " + state); @@ -6830,9 +6809,6 @@ 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 @@ -8153,6 +8129,10 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.onInitSensors(); break; + case MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS: + onPersistSpatialAudioDeviceSettings(); + break; + case MSG_CHECK_MUSIC_ACTIVE: onCheckMusicActive((String) msg.obj); break; @@ -8162,7 +8142,6 @@ public class AudioService extends IAudioService.Stub onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED), (String) msg.obj); break; - case MSG_PERSIST_SAFE_VOLUME_STATE: onPersistSafeVolumeState(msg.arg1); break; @@ -9123,103 +9102,42 @@ public class AudioService extends IAudioService.Stub } void onInitSpatializer() { - mDeviceBroker.onReadAudioDeviceSettings(); + final String settings = mSettings.getSecureStringForUser(mContentResolver, + Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT); + if (settings == null) { + Log.e(TAG, "error reading spatial audio device settings"); + } else { + Log.v(TAG, "restoring spatial audio device settings: " + settings); + mSpatializerHelper.setSADeviceSettings(settings); + } mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect); mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect); } - 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; - } - /** - * 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 + * post a message to persist the spatial audio device settings. + * Message is delayed by 1s on purpose in case of successive changes in quick succession (at + * init time for instance) + * Note this method is made public to work around a Mockito bug where it needs to be public + * in order to be mocked by a test a the same package + * (see https://code.google.com/archive/p/mockito/issues/127) */ - 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; + public void persistSpatialAudioDeviceSettings() { + sendMsg(mAudioHandler, + MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS, + SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0, TAG, + /*delay*/ 1000); } - private AudioDeviceAttributes anonymizeAudioDeviceAttributes(AudioDeviceAttributes ada) { - if (isBluetoothPrividged()) { - return ada; + void onPersistSpatialAudioDeviceSettings() { + final String settings = mSpatializerHelper.getSADeviceSettings(); + Log.v(TAG, "saving spatial audio device settings: " + settings); + boolean res = mSettings.putSecureStringForUser(mContentResolver, + Settings.Secure.SPATIAL_AUDIO_ENABLED, + settings, UserHandle.USER_CURRENT); + if (!res) { + Log.e(TAG, "error saving spatial audio device settings: " + settings); } - - return anonymizeAudioDeviceAttributesUnchecked(ada); } //========================================================================================== @@ -9249,16 +9167,13 @@ 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(ada)) { + if (mDeviceBroker.isDeviceConnected(device)) { // 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"); @@ -9270,26 +9185,19 @@ public class AudioService extends IAudioService.Stub + mMutingExpectedDevice); throw new IllegalStateException("muteAwaitConnection already in progress"); } - mMutingExpectedDevice = ada; + mMutingExpectedDevice = device; mMutedUsagesAwaitingConnection = usages; - mPlaybackMonitor.muteAwaitConnection(usages, ada, timeOutMs); + mPlaybackMonitor.muteAwaitConnection(usages, device, timeOutMs); } - dispatchMuteAwaitConnection((cb, isPrivileged) -> { - try { - AudioDeviceAttributes dev = ada; - if (!isPrivileged) { - dev = anonymizeAudioDeviceAttributesUnchecked(ada); - } - cb.dispatchOnMutedUntilConnection(dev, usages); - } catch (RemoteException e) { } - }); + dispatchMuteAwaitConnection(cb -> { try { + cb.dispatchOnMutedUntilConnection(device, usages); } catch (RemoteException e) { } }); } /** @see AudioManager#getMutingExpectedDevice */ public @Nullable AudioDeviceAttributes getMutingExpectedDevice() { enforceModifyAudioRoutingPermission(); synchronized (mMuteAwaitConnectionLock) { - return anonymizeAudioDeviceAttributes(mMutingExpectedDevice); + return mMutingExpectedDevice; } } @@ -9298,9 +9206,6 @@ 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) { @@ -9310,7 +9215,7 @@ public class AudioService extends IAudioService.Stub Log.i(TAG, "cancelMuteAwaitConnection ignored, no expected device"); return; } - if (!ada.equalTypeAddress(mMutingExpectedDevice)) { + if (!device.equalTypeAddress(mMutingExpectedDevice)) { Log.e(TAG, "cancelMuteAwaitConnection ignored, got " + device + "] but expected device is" + mMutingExpectedDevice); throw new IllegalStateException("cancelMuteAwaitConnection for wrong device"); @@ -9320,14 +9225,8 @@ public class AudioService extends IAudioService.Stub mMutedUsagesAwaitingConnection = null; mPlaybackMonitor.cancelMuteAwaitConnection("cancelMuteAwaitConnection dev:" + device); } - dispatchMuteAwaitConnection((cb, isPrivileged) -> { - try { - AudioDeviceAttributes dev = ada; - if (!isPrivileged) { - dev = anonymizeAudioDeviceAttributesUnchecked(ada); - } - cb.dispatchOnUnmutedEvent( - AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, dev, mutedUsages); + dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent( + AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, device, mutedUsages); } catch (RemoteException e) { } }); } @@ -9339,7 +9238,7 @@ public class AudioService extends IAudioService.Stub boolean register) { enforceModifyAudioRoutingPermission(); if (register) { - mMuteAwaitConnectionDispatchers.register(cb, isBluetoothPrividged()); + mMuteAwaitConnectionDispatchers.register(cb); } else { mMuteAwaitConnectionDispatchers.unregister(cb); } @@ -9363,14 +9262,8 @@ public class AudioService extends IAudioService.Stub mPlaybackMonitor.cancelMuteAwaitConnection( "checkMuteAwaitConnection device " + device + " connected, unmuting"); } - dispatchMuteAwaitConnection((cb, isPrivileged) -> { - try { - AudioDeviceAttributes ada = device; - if (!isPrivileged) { - ada = anonymizeAudioDeviceAttributesUnchecked(device); - } - cb.dispatchOnUnmutedEvent(AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION, - ada, mutedUsages); + dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent( + AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION, device, mutedUsages); } catch (RemoteException e) { } }); } @@ -9390,8 +9283,7 @@ public class AudioService extends IAudioService.Stub mMutingExpectedDevice = null; mMutedUsagesAwaitingConnection = null; } - dispatchMuteAwaitConnection((cb, isPrivileged) -> { - try { + dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent( AudioManager.MuteAwaitConnectionCallback.EVENT_TIMEOUT, timedOutDevice, mutedUsages); @@ -9399,14 +9291,13 @@ public class AudioService extends IAudioService.Stub } private void dispatchMuteAwaitConnection( - java.util.function.BiConsumer<IMuteAwaitConnectionCallback, Boolean> callback) { + java.util.function.Consumer<IMuteAwaitConnectionCallback> callback) { final int nbDispatchers = mMuteAwaitConnectionDispatchers.beginBroadcast(); // lazy initialization as errors unlikely ArrayList<IMuteAwaitConnectionCallback> errorList = null; for (int i = 0; i < nbDispatchers; i++) { try { - callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i), - (Boolean) mMuteAwaitConnectionDispatchers.getBroadcastCookie(i)); + callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i)); } catch (Exception e) { if (errorList == null) { errorList = new ArrayList<>(1); @@ -11659,9 +11550,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 @@ -11682,9 +11570,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()); @@ -11712,9 +11597,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()); diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index 6972b1f9b82b..5b26672c7de2 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -16,8 +16,6 @@ package com.android.server.audio; -import static android.media.AudioSystem.isBluetoothDevice; - import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -52,6 +50,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.UUID; /** @@ -71,12 +70,11 @@ public class SpatializerHelper { private final @NonNull AudioSystemAdapter mASA; private final @NonNull AudioService mAudioService; - private final @NonNull AudioDeviceBroker mDeviceBroker; private @Nullable SensorManager mSensorManager; //------------------------------------------------------------ - /*package*/ static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(15) { + private static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(15) { { append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL); append(AudioDeviceInfo.TYPE_WIRED_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL); @@ -98,6 +96,17 @@ public class SpatializerHelper { } }; + private static final int[] WIRELESS_TYPES = { AudioDeviceInfo.TYPE_BLUETOOTH_SCO, + AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, + AudioDeviceInfo.TYPE_BLE_HEADSET, + AudioDeviceInfo.TYPE_BLE_SPEAKER, + AudioDeviceInfo.TYPE_BLE_BROADCAST + }; + + private static final int[] WIRELESS_SPEAKER_TYPES = { + AudioDeviceInfo.TYPE_BLE_SPEAKER, + }; + // Spatializer state machine private static final int STATE_UNINITIALIZED = 0; private static final int STATE_NOT_SUPPORTED = 1; @@ -111,7 +120,6 @@ public class SpatializerHelper { /** current level as reported by native Spatializer in callback */ private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; - private boolean mTransauralSupported = false; private boolean mBinauralSupported = false; private boolean mIsHeadTrackingSupported = false; @@ -154,13 +162,17 @@ public class SpatializerHelper { */ private final ArrayList<Integer> mSACapableDeviceTypes = new ArrayList<>(0); + /** + * List of devices where Spatial Audio is possible. Each device can be enabled or disabled + * (== user choice to use or not) + */ + private final ArrayList<SADeviceState> mSADevices = new ArrayList<>(0); + //------------------------------------------------------ // initialization - SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa, - @NonNull AudioDeviceBroker deviceBroker) { + SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa) { mAudioService = mother; mASA = asa; - mDeviceBroker = deviceBroker; } synchronized void init(boolean effectExpected) { @@ -266,14 +278,6 @@ public class SpatializerHelper { mSACapableDeviceTypes.add(SPAT_MODE_FOR_DEVICE_TYPE.keyAt(i)); } } - - // Log the saved device states that are compatible with SA - for (AdiDeviceState deviceState : mDeviceBroker.getImmutableDeviceInventory()) { - if (isSADevice(deviceState)) { - logDeviceState(deviceState, "setSADeviceSettings"); - } - } - // for both transaural / binaural, we are not forcing enablement as the init() method // could have been called another time after boot in case of audioserver restart if (mTransauralSupported) { @@ -317,7 +321,7 @@ public class SpatializerHelper { mState = STATE_UNINITIALIZED; mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; - init(/*effectExpected=*/true); + init(true); setSpatializerEnabledInt(featureEnabled); } @@ -349,7 +353,7 @@ public class SpatializerHelper { DEFAULT_ATTRIBUTES, false /* forVolume */).toArray(ROUTING_DEVICES); // is media routed to a new device? - if (isBluetoothDevice(ROUTING_DEVICES[0].getInternalType())) { + if (isWireless(ROUTING_DEVICES[0].getType())) { addWirelessDeviceIfNew(ROUTING_DEVICES[0]); } @@ -493,9 +497,10 @@ public class SpatializerHelper { synchronized @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() { // build unionOf(mCompatibleAudioDevices, mEnabledDevice) - mDisabledAudioDevices ArrayList<AudioDeviceAttributes> compatList = new ArrayList<>(); - for (AdiDeviceState deviceState : mDeviceBroker.getImmutableDeviceInventory()) { - if (deviceState.isSAEnabled() && isSADevice(deviceState)) { - compatList.add(deviceState.getAudioDeviceAttributes()); + for (SADeviceState dev : mSADevices) { + if (dev.mEnabled) { + compatList.add(new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, + dev.mDeviceType, dev.mDeviceAddress == null ? "" : dev.mDeviceAddress)); } } return compatList; @@ -517,41 +522,34 @@ public class SpatializerHelper { private void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada, boolean forceEnable) { loglogi("addCompatibleAudioDevice: dev=" + ada); - final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); - initSAState(deviceState); - AdiDeviceState updatedDevice = null; // non-null on update. - if (deviceState != null) { - if (forceEnable && !deviceState.isSAEnabled()) { - updatedDevice = deviceState; - updatedDevice.setSAEnabled(true); - } - } else { - // When adding, force the device type to be a canonical one. - final int canonicalDeviceType = getCanonicalDeviceType(ada.getType(), - ada.getInternalType()); - if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) { - Log.e(TAG, "addCompatibleAudioDevice with incompatible AudioDeviceAttributes " - + ada); - return; + final int deviceType = ada.getType(); + final boolean wireless = isWireless(deviceType); + boolean isInList = false; + SADeviceState deviceUpdated = null; // non-null on update. + + for (SADeviceState deviceState : mSADevices) { + if (deviceType == deviceState.mDeviceType + && (!wireless || ada.getAddress().equals(deviceState.mDeviceAddress))) { + isInList = true; + if (forceEnable) { + deviceState.mEnabled = true; + deviceUpdated = deviceState; + } + break; } - updatedDevice = new AdiDeviceState(canonicalDeviceType, ada.getInternalType(), - ada.getAddress()); - initSAState(updatedDevice); - mDeviceBroker.addDeviceStateToInventory(updatedDevice); } - if (updatedDevice != null) { - onRoutingUpdated(); - mDeviceBroker.persistAudioDeviceSettings(); - logDeviceState(updatedDevice, "addCompatibleAudioDevice"); + if (!isInList) { + final SADeviceState dev = new SADeviceState(deviceType, + wireless ? ada.getAddress() : ""); + dev.mEnabled = true; + mSADevices.add(dev); + deviceUpdated = dev; } - } - - private void initSAState(AdiDeviceState device) { - if (device == null) { - return; + if (deviceUpdated != null) { + onRoutingUpdated(); + mAudioService.persistSpatialAudioDeviceSettings(); + logDeviceState(deviceUpdated, "addCompatibleAudioDevice"); } - device.setSAEnabled(true); - device.setHeadTrackerEnabled(true); } private static final String METRICS_DEVICE_PREFIX = "audio.spatializer.device."; @@ -561,57 +559,38 @@ public class SpatializerHelper { // // There may be different devices with the same device type (aliasing). // We always send the full device state info on each change. - static void logDeviceState(AdiDeviceState deviceState, String event) { - final String deviceName = AudioSystem.getDeviceName(deviceState.getInternalDeviceType()); + private void logDeviceState(SADeviceState deviceState, String event) { + final String deviceName = AudioSystem.getDeviceName(deviceState.mDeviceType); new MediaMetrics.Item(METRICS_DEVICE_PREFIX + deviceName) - .set(MediaMetrics.Property.ADDRESS, deviceState.getDeviceAddress()) - .set(MediaMetrics.Property.ENABLED, deviceState.isSAEnabled() ? "true" : "false") - .set(MediaMetrics.Property.EVENT, TextUtils.emptyIfNull(event)) - .set(MediaMetrics.Property.HAS_HEAD_TRACKER, - deviceState.hasHeadTracker() ? "true" - : "false") // this may be updated later. - .set(MediaMetrics.Property.HEAD_TRACKER_ENABLED, - deviceState.isHeadTrackerEnabled() ? "true" : "false") - .record(); + .set(MediaMetrics.Property.ADDRESS, deviceState.mDeviceAddress) + .set(MediaMetrics.Property.ENABLED, deviceState.mEnabled ? "true" : "false") + .set(MediaMetrics.Property.EVENT, TextUtils.emptyIfNull(event)) + .set(MediaMetrics.Property.HAS_HEAD_TRACKER, + deviceState.mHasHeadTracker ? "true" : "false") // this may be updated later. + .set(MediaMetrics.Property.HEAD_TRACKER_ENABLED, + deviceState.mHeadTrackerEnabled ? "true" : "false") + .record(); } synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { loglogi("removeCompatibleAudioDevice: dev=" + ada); - - final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); - if (deviceState != null && deviceState.isSAEnabled()) { - deviceState.setSAEnabled(false); - onRoutingUpdated(); - mDeviceBroker.persistAudioDeviceSettings(); - logDeviceState(deviceState, "removeCompatibleAudioDevice"); + final int deviceType = ada.getType(); + final boolean wireless = isWireless(deviceType); + SADeviceState deviceUpdated = null; // non-null on update. + + for (SADeviceState deviceState : mSADevices) { + if (deviceType == deviceState.mDeviceType + && (!wireless || ada.getAddress().equals(deviceState.mDeviceAddress))) { + deviceState.mEnabled = false; + deviceUpdated = deviceState; + break; + } } - } - - /** - * Returns a possibly aliased device type which is used - * for spatial audio settings (or TYPE_UNKNOWN if it doesn't exist). - */ - @AudioDeviceInfo.AudioDeviceType - private static int getCanonicalDeviceType(int deviceType, int internalDeviceType) { - if (isBluetoothDevice(internalDeviceType)) return deviceType; - - final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE); - if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) { - return AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; - } else if (spatMode == SpatializationMode.SPATIALIZER_BINAURAL) { - return AudioDeviceInfo.TYPE_WIRED_HEADPHONES; + if (deviceUpdated != null) { + onRoutingUpdated(); + mAudioService.persistSpatialAudioDeviceSettings(); + logDeviceState(deviceUpdated, "removeCompatibleAudioDevice"); } - return AudioDeviceInfo.TYPE_UNKNOWN; - } - - /** - * Returns the Spatial Audio device state for an audio device attributes - * or null if it does not exist. - */ - @Nullable - private AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada) { - return mDeviceBroker.findDeviceStateForAudioDeviceAttributes(ada, - getCanonicalDeviceType(ada.getType(), ada.getInternalType())); } /** @@ -623,7 +602,7 @@ public class SpatializerHelper { // if not a wireless device, this value will be overwritten to map the type // to TYPE_BUILTIN_SPEAKER or TYPE_WIRED_HEADPHONES @AudioDeviceInfo.AudioDeviceType int deviceType = ada.getType(); - final boolean wireless = isBluetoothDevice(ada.getInternalType()); + final boolean wireless = isWireless(deviceType); // if not a wireless device: find if media device is in the speaker, wired headphones if (!wireless) { @@ -646,8 +625,7 @@ public class SpatializerHelper { deviceType = AudioDeviceInfo.TYPE_WIRED_HEADPHONES; } } else { // wireless device - if (ada.getInternalType() == AudioSystem.DEVICE_OUT_BLE_SPEAKER - && !mTransauralSupported) { + if (isWirelessSpeaker(deviceType) && !mTransauralSupported) { Log.i(TAG, "Device incompatible with Spatial Audio (no transaural) dev:" + ada); return new Pair<>(false, false); @@ -659,35 +637,34 @@ public class SpatializerHelper { } } - final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); - if (deviceState == null) { - // no matching device state? - Log.i(TAG, "no spatialization device state found for Spatial Audio device:" + ada); - return new Pair<>(false, false); + boolean enabled = false; + boolean available = false; + for (SADeviceState deviceState : mSADevices) { + if (deviceType == deviceState.mDeviceType + && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress)) + || !wireless) { + available = true; + enabled = deviceState.mEnabled; + break; + } } - // found the matching device state. - return new Pair<>(deviceState.isSAEnabled(), true); + return new Pair<>(enabled, available); } private synchronized void addWirelessDeviceIfNew(@NonNull AudioDeviceAttributes ada) { - if (!isDeviceCompatibleWithSpatializationModes(ada)) { - return; - } - if (findDeviceStateForAudioDeviceAttributes(ada) == null) { - // wireless device types should be canonical, but we translate to be sure. - final int canonicalDeviceType = getCanonicalDeviceType(ada.getType(), - ada.getInternalType()); - if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) { - Log.e(TAG, "addWirelessDeviceIfNew with incompatible AudioDeviceAttributes " - + ada); - return; + boolean knownDevice = false; + for (SADeviceState deviceState : mSADevices) { + // wireless device so always check address + if (ada.getType() == deviceState.mDeviceType + && ada.getAddress().equals(deviceState.mDeviceAddress)) { + knownDevice = true; + break; } - final AdiDeviceState deviceState = - new AdiDeviceState(canonicalDeviceType, ada.getInternalType(), - ada.getAddress()); - initSAState(deviceState); - mDeviceBroker.addDeviceStateToInventory(deviceState); - mDeviceBroker.persistAudioDeviceSettings(); + } + if (!knownDevice) { + final SADeviceState deviceState = new SADeviceState(ada.getType(), ada.getAddress()); + mSADevices.add(deviceState); + mAudioService.persistSpatialAudioDeviceSettings(); logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later. } } @@ -728,7 +705,16 @@ public class SpatializerHelper { return false; } - return findDeviceStateForAudioDeviceAttributes(ada) != null; + final int deviceType = ada.getType(); + final boolean wireless = isWireless(deviceType); + for (SADeviceState deviceState : mSADevices) { + if (deviceType == deviceState.mDeviceType + && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress)) + || !wireless) { + return true; + } + } + return false; } private synchronized boolean canBeSpatializedOnDevice(@NonNull AudioAttributes attributes, @@ -743,26 +729,6 @@ public class SpatializerHelper { return false; } - private boolean isSADevice(AdiDeviceState deviceState) { - return deviceState.getDeviceType() == getCanonicalDeviceType(deviceState.getDeviceType(), - deviceState.getInternalDeviceType()) && isDeviceCompatibleWithSpatializationModes( - deviceState.getAudioDeviceAttributes()); - } - - private boolean isDeviceCompatibleWithSpatializationModes(@NonNull AudioDeviceAttributes ada) { - // modeForDevice will be neither transaural or binaural for devices that do not support - // spatial audio. For instance mono devices like earpiece, speaker safe or sco must - // not be included. - final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(ada.getType(), - /*default when type not found*/ -1); - if ((modeForDevice == SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported) - || (modeForDevice == SpatializationMode.SPATIALIZER_TRANSAURAL - && mTransauralSupported)) { - return true; - } - return false; - } - synchronized void setFeatureEnabled(boolean enabled) { loglogi("setFeatureEnabled(" + enabled + ") was featureEnabled:" + mFeatureEnabled); if (mFeatureEnabled == enabled) { @@ -1123,20 +1089,27 @@ public class SpatializerHelper { Log.v(TAG, "no headtracking support, ignoring setHeadTrackerEnabled to " + enabled + " for " + ada); } - final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); - if (deviceState == null) return; - if (!deviceState.hasHeadTracker()) { - Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled - + " device:" + ada + " on a device without headtracker"); - return; - } - Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada); - deviceState.setHeadTrackerEnabled(enabled); - mDeviceBroker.persistAudioDeviceSettings(); - logDeviceState(deviceState, "setHeadTrackerEnabled"); + final int deviceType = ada.getType(); + final boolean wireless = isWireless(deviceType); + for (SADeviceState deviceState : mSADevices) { + if (deviceType == deviceState.mDeviceType + && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress)) + || !wireless) { + if (!deviceState.mHasHeadTracker) { + Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled + + " device:" + ada + " on a device without headtracker"); + return; + } + Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada); + deviceState.mHeadTrackerEnabled = enabled; + mAudioService.persistSpatialAudioDeviceSettings(); + logDeviceState(deviceState, "setHeadTrackerEnabled"); + break; + } + } // check current routing to see if it affects the headtracking mode - if (ROUTING_DEVICES[0].getType() == ada.getType() + if (ROUTING_DEVICES[0].getType() == deviceType && ROUTING_DEVICES[0].getAddress().equals(ada.getAddress())) { setDesiredHeadTrackingMode(enabled ? mDesiredHeadTrackingModeWhenEnabled : Spatializer.HEAD_TRACKING_MODE_DISABLED); @@ -1148,8 +1121,17 @@ public class SpatializerHelper { Log.v(TAG, "no headtracking support, hasHeadTracker always false for " + ada); return false; } - final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); - return deviceState != null && deviceState.hasHeadTracker(); + final int deviceType = ada.getType(); + final boolean wireless = isWireless(deviceType); + + for (SADeviceState deviceState : mSADevices) { + if (deviceType == deviceState.mDeviceType + && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress)) + || !wireless) { + return deviceState.mHasHeadTracker; + } + } + return false; } /** @@ -1162,14 +1144,20 @@ public class SpatializerHelper { Log.v(TAG, "no headtracking support, setHasHeadTracker always false for " + ada); return false; } - final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); - if (deviceState != null) { - if (!deviceState.hasHeadTracker()) { - deviceState.setHasHeadTracker(true); - mDeviceBroker.persistAudioDeviceSettings(); - logDeviceState(deviceState, "setHasHeadTracker"); + final int deviceType = ada.getType(); + final boolean wireless = isWireless(deviceType); + + for (SADeviceState deviceState : mSADevices) { + if (deviceType == deviceState.mDeviceType + && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress)) + || !wireless) { + if (!deviceState.mHasHeadTracker) { + deviceState.mHasHeadTracker = true; + mAudioService.persistSpatialAudioDeviceSettings(); + logDeviceState(deviceState, "setHasHeadTracker"); + } + return deviceState.mHeadTrackerEnabled; } - return deviceState.isHeadTrackerEnabled(); } Log.e(TAG, "setHasHeadTracker: device not found for:" + ada); return false; @@ -1180,9 +1168,20 @@ public class SpatializerHelper { Log.v(TAG, "no headtracking support, isHeadTrackerEnabled always false for " + ada); return false; } - final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); - return deviceState != null - && deviceState.hasHeadTracker() && deviceState.isHeadTrackerEnabled(); + final int deviceType = ada.getType(); + final boolean wireless = isWireless(deviceType); + + for (SADeviceState deviceState : mSADevices) { + if (deviceType == deviceState.mDeviceType + && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress)) + || !wireless) { + if (!deviceState.mHasHeadTracker) { + return false; + } + return deviceState.mHeadTrackerEnabled; + } + } + return false; } synchronized boolean isHeadTrackerAvailable() { @@ -1514,6 +1513,117 @@ public class SpatializerHelper { pw.println("\tsupports binaural:" + mBinauralSupported + " / transaural:" + mTransauralSupported); pw.println("\tmSpatOutput:" + mSpatOutput); + pw.println("\tdevices:"); + for (SADeviceState device : mSADevices) { + pw.println("\t\t" + device); + } + } + + /*package*/ static final class SADeviceState { + final @AudioDeviceInfo.AudioDeviceType int mDeviceType; + final @NonNull String mDeviceAddress; + boolean mEnabled = true; // by default, SA is enabled on any device + boolean mHasHeadTracker = false; + boolean mHeadTrackerEnabled = true; // by default, if head tracker is present, use it + static final String SETTING_FIELD_SEPARATOR = ","; + static final String SETTING_DEVICE_SEPARATOR_CHAR = "|"; + static final String SETTING_DEVICE_SEPARATOR = "\\|"; + + SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @NonNull String address) { + mDeviceType = deviceType; + mDeviceAddress = Objects.requireNonNull(address); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + // type check and cast + if (getClass() != obj.getClass()) { + return false; + } + final SADeviceState sads = (SADeviceState) obj; + return mDeviceType == sads.mDeviceType + && mDeviceAddress.equals(sads.mDeviceAddress) + && mEnabled == sads.mEnabled + && mHasHeadTracker == sads.mHasHeadTracker + && mHeadTrackerEnabled == sads.mHeadTrackerEnabled; + } + + @Override + public int hashCode() { + return Objects.hash(mDeviceType, mDeviceAddress, mEnabled, mHasHeadTracker, + mHeadTrackerEnabled); + } + + @Override + public String toString() { + return "type:" + mDeviceType + " addr:" + mDeviceAddress + " enabled:" + mEnabled + + " HT:" + mHasHeadTracker + " HTenabled:" + mHeadTrackerEnabled; + } + + String toPersistableString() { + return (new StringBuilder().append(mDeviceType) + .append(SETTING_FIELD_SEPARATOR).append(mDeviceAddress) + .append(SETTING_FIELD_SEPARATOR).append(mEnabled ? "1" : "0") + .append(SETTING_FIELD_SEPARATOR).append(mHasHeadTracker ? "1" : "0") + .append(SETTING_FIELD_SEPARATOR).append(mHeadTrackerEnabled ? "1" : "0") + .toString()); + } + + static @Nullable SADeviceState fromPersistedString(@Nullable String persistedString) { + if (persistedString == null) { + return null; + } + if (persistedString.isEmpty()) { + return null; + } + String[] fields = TextUtils.split(persistedString, SETTING_FIELD_SEPARATOR); + if (fields.length != 5) { + // expecting all fields, fewer may mean corruption, ignore those settings + return null; + } + try { + final int deviceType = Integer.parseInt(fields[0]); + final SADeviceState deviceState = new SADeviceState(deviceType, fields[1]); + deviceState.mEnabled = Integer.parseInt(fields[2]) == 1; + deviceState.mHasHeadTracker = Integer.parseInt(fields[3]) == 1; + deviceState.mHeadTrackerEnabled = Integer.parseInt(fields[4]) == 1; + return deviceState; + } catch (NumberFormatException e) { + Log.e(TAG, "unable to parse setting for SADeviceState: " + persistedString, e); + return null; + } + } + } + + /*package*/ synchronized String getSADeviceSettings() { + // expected max size of each String for each SADeviceState is 25 (accounting for separator) + final StringBuilder settingsBuilder = new StringBuilder(mSADevices.size() * 25); + for (int i = 0; i < mSADevices.size(); i++) { + settingsBuilder.append(mSADevices.get(i).toPersistableString()); + if (i != mSADevices.size() - 1) { + settingsBuilder.append(SADeviceState.SETTING_DEVICE_SEPARATOR_CHAR); + } + } + return settingsBuilder.toString(); + } + + /*package*/ synchronized void setSADeviceSettings(@NonNull String persistedSettings) { + String[] devSettings = TextUtils.split(Objects.requireNonNull(persistedSettings), + SADeviceState.SETTING_DEVICE_SEPARATOR); + // small list, not worth overhead of Arrays.stream(devSettings) + for (String setting : devSettings) { + SADeviceState devState = SADeviceState.fromPersistedString(setting); + if (devState != null) { + mSADevices.add(devState); + logDeviceState(devState, "setSADeviceSettings"); + } + } } private static String spatStateString(int state) { @@ -1535,6 +1645,24 @@ public class SpatializerHelper { } } + private static boolean isWireless(@AudioDeviceInfo.AudioDeviceType int deviceType) { + for (int type : WIRELESS_TYPES) { + if (type == deviceType) { + return true; + } + } + return false; + } + + private static boolean isWirelessSpeaker(@AudioDeviceInfo.AudioDeviceType int deviceType) { + for (int type : WIRELESS_SPEAKER_TYPES) { + if (type == deviceType) { + return true; + } + } + return false; + } + private int getHeadSensorHandleUpdateTracker() { int headHandle = -1; UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(ROUTING_DEVICES[0]); @@ -1579,4 +1707,11 @@ public class SpatializerHelper { AudioService.sSpatialLogger.loglog(msg, AudioEventLogger.Event.ALOGE, TAG); return msg; } + + //------------------------------------------------ + // for testing purposes only + + /*package*/ void clearSADevices() { + mSADevices.clear(); + } } diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java index b6b987c01a4a..dad9fe8648b2 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java @@ -29,14 +29,13 @@ import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.Intent; import android.media.AudioDeviceAttributes; -import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.AudioSystem; import android.media.BluetoothProfileConnectionInfo; import android.util.Log; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; -import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import org.junit.After; @@ -55,6 +54,7 @@ public class AudioDeviceBrokerTest { private static final String TAG = "AudioDeviceBrokerTest"; private static final int MAX_MESSAGE_HANDLING_DELAY_MS = 100; + private Context mContext; // the actual class under test private AudioDeviceBroker mAudioDeviceBroker; @@ -67,13 +67,13 @@ public class AudioDeviceBrokerTest { @Before public void setUp() throws Exception { - Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mContext = InstrumentationRegistry.getTargetContext(); mMockAudioService = mock(AudioService.class); mSpyAudioSystem = spy(new NoOpAudioSystemAdapter()); mSpyDevInventory = spy(new AudioDeviceInventory(mSpyAudioSystem)); mSpySystemServer = spy(new NoOpSystemServerAdapter()); - mAudioDeviceBroker = new AudioDeviceBroker(context, mMockAudioService, mSpyDevInventory, + mAudioDeviceBroker = new AudioDeviceBroker(mContext, mMockAudioService, mSpyDevInventory, mSpySystemServer); mSpyDevInventory.setDeviceBroker(mAudioDeviceBroker); @@ -197,37 +197,6 @@ public class AudioDeviceBrokerTest { any(Intent.class)); } - /** - * Test that constructing an AdiDeviceState instance requires a non-null address for a - * wireless type, but can take null for a non-wireless type; - * @throws Exception - */ - @Test - public void testAdiDeviceStateNullAddressCtor() throws Exception { - try { - new AdiDeviceState(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, - AudioManager.DEVICE_OUT_SPEAKER, null); - new AdiDeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, - AudioManager.DEVICE_OUT_BLUETOOTH_A2DP, null); - Assert.fail(); - } catch (NullPointerException e) { } - } - - @Test - public void testAdiDeviceStateStringSerialization() throws Exception { - Log.i(TAG, "starting testAdiDeviceStateStringSerialization"); - final AdiDeviceState devState = new AdiDeviceState( - AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, AudioManager.DEVICE_OUT_SPEAKER, "bla"); - devState.setHasHeadTracker(false); - devState.setHeadTrackerEnabled(false); - devState.setSAEnabled(true); - final String persistString = devState.toPersistableString(); - final AdiDeviceState result = AdiDeviceState.fromPersistedString(persistString); - Log.i(TAG, "original:" + devState); - Log.i(TAG, "result :" + result); - Assert.assertEquals(devState, result); - } - private void doTestConnectionDisconnectionReconnection(int delayAfterDisconnection, boolean mockMediaPlayback, boolean guaranteeSingleConnection) throws Exception { when(mMockAudioService.getDeviceForStream(AudioManager.STREAM_MUSIC)) diff --git a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java index bf38fd1669ad..b17c3a18d89e 100644 --- a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java @@ -15,19 +15,18 @@ */ package com.android.server.audio; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; +import com.android.server.audio.SpatializerHelper.SADeviceState; + import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import android.media.AudioDeviceAttributes; -import android.media.AudioFormat; +import android.media.AudioDeviceInfo; import android.media.AudioSystem; import android.util.Log; import androidx.test.filters.MediumTest; -import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import org.junit.Assert; @@ -50,23 +49,41 @@ public class SpatializerHelperTest { @Mock private AudioService mMockAudioService; @Spy private AudioSystemAdapter mSpyAudioSystem; - @Spy private AudioDeviceBroker mSpyDeviceBroker; @Before public void setUp() throws Exception { mMockAudioService = mock(AudioService.class); - mSpyAudioSystem = spy(new NoOpAudioSystemAdapter()); - mSpyDeviceBroker = spy( - new AudioDeviceBroker( - InstrumentationRegistry.getInstrumentation().getTargetContext(), - mMockAudioService)); - mSpatHelper = new SpatializerHelper(mMockAudioService, mSpyAudioSystem, - mSpyDeviceBroker); + + mSpatHelper = new SpatializerHelper(mMockAudioService, mSpyAudioSystem); + } + + @Test + public void testSADeviceStateNullAddressCtor() throws Exception { + try { + SADeviceState devState = new SADeviceState( + AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null); + Assert.fail(); + } catch (NullPointerException e) { } + } + + @Test + public void testSADeviceStateStringSerialization() throws Exception { + Log.i(TAG, "starting testSADeviceStateStringSerialization"); + final SADeviceState devState = new SADeviceState( + AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "bla"); + devState.mHasHeadTracker = false; + devState.mHeadTrackerEnabled = false; + devState.mEnabled = true; + final String persistString = devState.toPersistableString(); + final SADeviceState result = SADeviceState.fromPersistedString(persistString); + Log.i(TAG, "original:" + devState); + Log.i(TAG, "result :" + result); + Assert.assertEquals(devState, result); } @Test - public void testAdiDeviceStateSettings() throws Exception { + public void testSADeviceSettings() throws Exception { Log.i(TAG, "starting testSADeviceSettings"); final AudioDeviceAttributes dev1 = new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""); @@ -75,7 +92,7 @@ public class SpatializerHelperTest { final AudioDeviceAttributes dev3 = new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "R2:D2:bloop"); - doNothing().when(mSpyDeviceBroker).persistAudioDeviceSettings(); + doNothing().when(mMockAudioService).persistSpatialAudioDeviceSettings(); // test with single device mSpatHelper.addCompatibleAudioDevice(dev1); @@ -109,11 +126,11 @@ public class SpatializerHelperTest { * the original one. */ private void checkAddSettings() throws Exception { - String settings = mSpyDeviceBroker.getDeviceSettings(); + String settings = mSpatHelper.getSADeviceSettings(); Log.i(TAG, "device settings: " + settings); - mSpyDeviceBroker.clearDeviceInventory(); - mSpyDeviceBroker.setDeviceSettings(settings); - String settingsRestored = mSpyDeviceBroker.getDeviceSettings(); + mSpatHelper.clearSADevices(); + mSpatHelper.setSADeviceSettings(settings); + String settingsRestored = mSpatHelper.getSADeviceSettings(); Log.i(TAG, "device settingsRestored: " + settingsRestored); Assert.assertEquals(settings, settingsRestored); } |