summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVlad Popa <pvlad@google.com>2023-06-26 18:52:59 -0700
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-12-06 22:44:11 +0000
commit32f73f2944b76c8419d359d383f83281bcf179bf (patch)
tree883efe9a2ba94bc2695a3489325f6bc105d04b01
parent55fc00a0788ea0995fe0851616b9ac21710a2931 (diff)
downloadbase-32f73f2944b76c8419d359d383f83281bcf179bf.tar.gz
Refactor the SADeviceState to AdiDeviceState
The idea is to have a device state catalog for all the known devices. Also refactored the name of the Settings.Secure key entry for persistence. The current code will check the legacy key first, erase it and update the new key. Test: atest SpatializerHelperTest & AudioDeviceBrokerTest Bug: 278265907 Bug: 285588444 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:61b2a9219e0b7c8899068d1faa33da3bdc0769b4) Merged-In: Idabcc84cb0f5f6f88ba5aebc435511ab95016ef3 Change-Id: Idabcc84cb0f5f6f88ba5aebc435511ab95016ef3
-rw-r--r--core/java/android/provider/Settings.java7
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java1
-rw-r--r--services/core/java/com/android/server/audio/AdiDeviceState.java204
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java104
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java92
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java56
-rw-r--r--services/core/java/com/android/server/audio/SpatializerHelper.java355
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java39
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java86
9 files changed, 576 insertions, 368 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d695c0cb3760..1374fa0e92c4 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9926,6 +9926,13 @@ 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/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 873b434aa4fd..b957bbddf9d6 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -700,6 +700,7 @@ 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
new file mode 100644
index 000000000000..683b3eb7a92e
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AdiDeviceState.java
@@ -0,0 +1,204 @@
+/*
+ * 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 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;
+ 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) : "";
+ }
+
+ @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: " + 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 29a19417b8fd..c67bf3c5edff 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -50,6 +50,7 @@ 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;
@@ -69,8 +70,11 @@ import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
-/** @hide */
-/*package*/ final class AudioDeviceBroker {
+/**
+ * @hide
+ * (non final for mocking/spying)
+ */
+public class AudioDeviceBroker {
private static final String TAG = "AS.AudioDeviceBroker";
@@ -1850,6 +1854,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
BtHelper.onNotifyPreferredAudioProfileApplied(btDevice);
} break;
+ case MSG_PERSIST_AUDIO_DEVICE_SETTINGS:
+ onPersistAudioDeviceSettings();
+ break;
default:
Log.wtf(TAG, "Invalid message " + msg.what);
}
@@ -1927,6 +1934,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52;
+ 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:
@@ -2344,4 +2353,95 @@ import java.util.concurrent.atomic.AtomicBoolean;
info.getId(),
null /*mixerAttributes*/);
}
+
+ /**
+ * 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 0c7f11f98809..387646cccd7c 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -15,6 +15,8 @@
*/
package com.android.server.audio;
+import static android.media.AudioSystem.isBluetoothDevice;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
@@ -78,12 +80,54 @@ 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 ArrayList<AdiDeviceState> mDeviceInventory = new ArrayList<>(0);
+
+
+ List<AdiDeviceState> getImmutableDeviceInventory() {
+ synchronized (mDeviceInventoryLock) {
+ return List.copyOf(mDeviceInventory);
+ }
+ }
+
+ void addDeviceStateToInventory(AdiDeviceState deviceState) {
+ synchronized (mDeviceInventoryLock) {
+ mDeviceInventory.add(deviceState);
+ }
+ }
+
+ AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada,
+ int canonicalDeviceType) {
+ final boolean isWireless = isBluetoothDevice(ada.getInternalType());
+
+ synchronized (mDeviceInventoryLock) {
+ for (AdiDeviceState deviceSetting : mDeviceInventory) {
+ if (deviceSetting.getDeviceType() == canonicalDeviceType
+ && (!isWireless || ada.getAddress().equals(
+ deviceSetting.getDeviceAddress()))) {
+ return deviceSetting;
+ }
+ }
+ }
+ return null;
+ }
+
+ void clearDeviceInventory() {
+ synchronized (mDeviceInventoryLock) {
+ mDeviceInventory.clear();
+ }
+ }
+
// List of connected devices
// Key for map created from DeviceInfo.makeDeviceListKey()
@GuardedBy("mDevicesLock")
@@ -341,6 +385,12 @@ public class AudioDeviceInventory {
mAppliedPresetRolesInt.forEach((key, devices) -> {
pw.println(" " + prefix + "preset: " + key.first
+ " role:" + key.second + " devices:" + devices); });
+ pw.println("\ndevices:\n");
+ synchronized (mDeviceInventoryLock) {
+ for (AdiDeviceState device : mDeviceInventory) {
+ pw.println("\t" + device + "\n");
+ }
+ }
}
//------------------------------------------------------------
@@ -1198,7 +1248,7 @@ public class AudioDeviceInventory {
AudioDeviceInfo device = Stream.of(connectedDevices)
.filter(d -> d.getInternalType() == ada.getInternalType())
- .filter(d -> (!AudioSystem.isBluetoothDevice(d.getInternalType())
+ .filter(d -> (!isBluetoothDevice(d.getInternalType())
|| (d.getAddress().equals(ada.getAddress()))))
.findFirst()
.orElse(null);
@@ -1619,7 +1669,7 @@ public class AudioDeviceInventory {
}
for (DeviceInfo di : mConnectedDevices.values()) {
- if (!AudioSystem.isBluetoothDevice(di.mDeviceType)) {
+ if (!isBluetoothDevice(di.mDeviceType)) {
continue;
}
AudioDeviceAttributes ada =
@@ -1733,7 +1783,7 @@ public class AudioDeviceInventory {
}
HashSet<String> processedAddresses = new HashSet<>(0);
for (DeviceInfo di : mConnectedDevices.values()) {
- if (!AudioSystem.isBluetoothDevice(di.mDeviceType)
+ if (!isBluetoothDevice(di.mDeviceType)
|| processedAddresses.contains(di.mDeviceAddress)) {
continue;
}
@@ -1743,7 +1793,7 @@ public class AudioDeviceInventory {
+ di.mDeviceAddress + ", preferredProfiles: " + preferredProfiles);
}
for (DeviceInfo di2 : mConnectedDevices.values()) {
- if (!AudioSystem.isBluetoothDevice(di2.mDeviceType)
+ if (!isBluetoothDevice(di2.mDeviceType)
|| !di.mDeviceAddress.equals(di2.mDeviceAddress)) {
continue;
}
@@ -2359,6 +2409,40 @@ public class AudioDeviceInventory {
}
}
+ /*package*/ String getDeviceSettings() {
+ int deviceCatalogSize = 0;
+ synchronized (mDeviceInventoryLock) {
+ deviceCatalogSize = mDeviceInventory.size();
+ }
+ final StringBuilder settingsBuilder = new StringBuilder(
+ deviceCatalogSize * AdiDeviceState.getPeristedMaxSize());
+
+ synchronized (mDeviceInventoryLock) {
+ for (int i = 0; i < mDeviceInventory.size(); i++) {
+ settingsBuilder.append(mDeviceInventory.get(i).toPersistableString());
+ if (i != mDeviceInventory.size() - 1) {
+ settingsBuilder.append(SETTING_DEVICE_SEPARATOR_CHAR);
+ }
+ }
+ }
+ 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 53ed38edffe4..e2e979e79898 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -381,7 +381,6 @@ 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;
@@ -1021,6 +1020,8 @@ public class AudioService extends IAudioService.Stub
mAudioPolicy = audioPolicy;
mPlatformType = AudioSystem.getPlatformType(context);
+ mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
+
mIsSingleVolume = AudioSystem.isSingleVolume(context);
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
@@ -1033,13 +1034,14 @@ public class AudioService extends IAudioService.Stub
mSfxHelper = new SoundEffectsHelper(mContext, playerBase -> ignorePlayerLogs(playerBase));
- final boolean binauralEnabledDefault = SystemProperties.getBoolean(
+ boolean binauralEnabledDefault = SystemProperties.getBoolean(
"ro.audio.spatializer_binaural_enabled_default", true);
- final boolean transauralEnabledDefault = SystemProperties.getBoolean(
+ boolean transauralEnabledDefault = SystemProperties.getBoolean(
"ro.audio.spatializer_transaural_enabled_default", true);
- final boolean headTrackingEnabledDefault = mContext.getResources().getBoolean(
+ boolean headTrackingEnabledDefault = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_spatial_audio_head_tracking_enabled_default);
- mSpatializerHelper = new SpatializerHelper(this, mAudioSystem,
+
+ mSpatializerHelper = new SpatializerHelper(this, mAudioSystem, mDeviceBroker,
binauralEnabledDefault, transauralEnabledDefault, headTrackingEnabledDefault);
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
@@ -1207,8 +1209,6 @@ public class AudioService extends IAudioService.Stub
mUseFixedVolume = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
- mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
-
mRecordMonitor = new RecordingActivityMonitor(mContext);
mRecordMonitor.registerRecordingCallback(mVoiceRecordingActivityMonitor, true);
@@ -6567,6 +6567,10 @@ public class AudioService extends IAudioService.Stub
return mContentResolver;
}
+ /*package*/ SettingsAdapter getSettings() {
+ return mSettings;
+ }
+
///////////////////////////////////////////////////////////////////////////
// Internal methods
///////////////////////////////////////////////////////////////////////////
@@ -9209,10 +9213,6 @@ public class AudioService extends IAudioService.Stub
mSpatializerHelper.onInitSensors();
break;
- case MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS:
- onPersistSpatialAudioDeviceSettings();
- break;
-
case MSG_RESET_SPATIALIZER:
mSpatializerHelper.reset(/* featureEnabled */ mHasSpatializerEffect);
break;
@@ -10269,41 +10269,11 @@ public class AudioService extends IAudioService.Stub
}
void onInitSpatializer() {
- 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");
- }
- mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect, settings);
+ mDeviceBroker.onReadAudioDeviceSettings();
+ mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect);
mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
}
- /**
- * 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)
- */
- public void persistSpatialAudioDeviceSettings() {
- sendMsg(mAudioHandler,
- MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS,
- SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0, TAG,
- /*delay*/ 1000);
- }
-
- 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);
- }
- }
-
//==========================================================================================
// camera sound is forced if any of the resources corresponding to one active SIM
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 462c9381b904..969dd60a8012 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -16,6 +16,8 @@
package com.android.server.audio;
+import static android.media.AudioSystem.isBluetoothDevice;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -47,13 +49,13 @@ import android.util.Pair;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
-import java.util.Objects;
import java.util.UUID;
/**
@@ -73,11 +75,12 @@ public class SpatializerHelper {
private final @NonNull AudioSystemAdapter mASA;
private final @NonNull AudioService mAudioService;
+ private final @NonNull AudioDeviceBroker mDeviceBroker;
private @Nullable SensorManager mSensorManager;
//------------------------------------------------------------
- private static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) {
+ /*package*/ static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) {
{
append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL);
append(AudioDeviceInfo.TYPE_WIRED_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
@@ -98,13 +101,6 @@ 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
- };
-
// Spatializer state machine
/*package*/ static final int STATE_UNINITIALIZED = 0;
/*package*/ static final int STATE_NOT_SUPPORTED = 1;
@@ -114,10 +110,15 @@ public class SpatializerHelper {
/*package*/ static final int STATE_DISABLED_AVAILABLE = 6;
private int mState = STATE_UNINITIALIZED;
+ @VisibleForTesting boolean mBinauralEnabledDefault;
+ @VisibleForTesting boolean mTransauralEnabledDefault;
+ @VisibleForTesting boolean mHeadTrackingEnabledDefault;
+
private boolean mFeatureEnabled = false;
/** 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;
@@ -160,31 +161,21 @@ 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)
- */
- @GuardedBy("this")
- private final ArrayList<SADeviceState> mSADevices = new ArrayList<>(0);
-
//------------------------------------------------------
// initialization
- @SuppressWarnings("StaticAssignmentInConstructor")
SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa,
- boolean binauralEnabledDefault,
- boolean transauralEnabledDefault,
- boolean headTrackingEnabledDefault) {
+ @NonNull AudioDeviceBroker deviceBroker, boolean binauralEnabledDefault,
+ boolean transauralEnabledDefault, boolean headTrackingEnabledDefault) {
mAudioService = mother;
mASA = asa;
- // "StaticAssignmentInConstructor" warning is suppressed as the SpatializerHelper being
- // constructed here is the factory for SADeviceState, thus SADeviceState and its
- // private static field sHeadTrackingEnabledDefault should never be accessed directly.
- SADeviceState.sBinauralEnabledDefault = binauralEnabledDefault;
- SADeviceState.sTransauralEnabledDefault = transauralEnabledDefault;
- SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledDefault;
+ mDeviceBroker = deviceBroker;
+
+ mBinauralEnabledDefault = binauralEnabledDefault;
+ mTransauralEnabledDefault = transauralEnabledDefault;
+ mHeadTrackingEnabledDefault = headTrackingEnabledDefault;
}
- synchronized void init(boolean effectExpected, @Nullable String settings) {
+ synchronized void init(boolean effectExpected) {
loglogi("init effectExpected=" + effectExpected);
if (!effectExpected) {
loglogi("init(): setting state to STATE_NOT_SUPPORTED due to effect not expected");
@@ -288,10 +279,11 @@ public class SpatializerHelper {
}
}
- // When initialized from AudioService, the settings string will be non-null.
- // Saved settings need to be applied after spatialization support is initialized above.
- if (settings != null) {
- setSADeviceSettings(settings);
+ // 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
@@ -331,7 +323,7 @@ public class SpatializerHelper {
mState = STATE_UNINITIALIZED;
mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
- init(true, null /* settings */);
+ init(/*effectExpected=*/true);
setSpatializerEnabledInt(featureEnabled);
}
@@ -372,7 +364,7 @@ public class SpatializerHelper {
final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
// is media routed to a new device?
- if (isWireless(currentDevice.getType())) {
+ if (isBluetoothDevice(currentDevice.getInternalType())) {
addWirelessDeviceIfNew(currentDevice);
}
@@ -520,8 +512,8 @@ public class SpatializerHelper {
synchronized @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() {
// build unionOf(mCompatibleAudioDevices, mEnabledDevice) - mDisabledAudioDevices
ArrayList<AudioDeviceAttributes> compatList = new ArrayList<>();
- for (SADeviceState deviceState : mSADevices) {
- if (deviceState.mEnabled) {
+ for (AdiDeviceState deviceState : mDeviceBroker.getImmutableDeviceInventory()) {
+ if (deviceState.isSAEnabled() && isSADevice(deviceState)) {
compatList.add(deviceState.getAudioDeviceAttributes());
}
}
@@ -548,29 +540,48 @@ public class SpatializerHelper {
return;
}
loglogi("addCompatibleAudioDevice: dev=" + ada);
- final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
- SADeviceState deviceUpdated = null; // non-null on update.
+ final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ initSAState(deviceState);
+ AdiDeviceState updatedDevice = null; // non-null on update.
if (deviceState != null) {
- if (forceEnable && !deviceState.mEnabled) {
- deviceUpdated = deviceState;
- deviceUpdated.mEnabled = true;
+ 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());
+ final int canonicalDeviceType = getCanonicalDeviceType(ada.getType(),
+ ada.getInternalType());
if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) {
Log.e(TAG, "addCompatibleAudioDevice with incompatible AudioDeviceAttributes "
+ ada);
return;
}
- deviceUpdated = new SADeviceState(canonicalDeviceType, ada.getAddress());
- mSADevices.add(deviceUpdated);
+ updatedDevice = new AdiDeviceState(canonicalDeviceType, ada.getInternalType(),
+ ada.getAddress());
+ initSAState(updatedDevice);
+ mDeviceBroker.addDeviceStateToInventory(updatedDevice);
}
- if (deviceUpdated != null) {
+ if (updatedDevice != null) {
onRoutingUpdated();
- mAudioService.persistSpatialAudioDeviceSettings();
- logDeviceState(deviceUpdated, "addCompatibleAudioDevice");
+ mDeviceBroker.persistAudioDeviceSettings();
+ logDeviceState(updatedDevice, "addCompatibleAudioDevice");
+ }
+ }
+
+ private void initSAState(AdiDeviceState device) {
+ if (device == null) {
+ return;
}
+
+ int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(device.getDeviceType(),
+ Integer.MIN_VALUE);
+ device.setSAEnabled(spatMode == SpatializationMode.SPATIALIZER_BINAURAL
+ ? mBinauralEnabledDefault
+ : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL
+ ? mTransauralEnabledDefault
+ : false);
+ device.setHeadTrackerEnabled(mHeadTrackingEnabledDefault);
}
private static final String METRICS_DEVICE_PREFIX = "audio.spatializer.device.";
@@ -580,29 +591,30 @@ 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.
- private void logDeviceState(SADeviceState deviceState, String event) {
+ static void logDeviceState(AdiDeviceState deviceState, String event) {
final int deviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
- deviceState.mDeviceType);
+ deviceState.getDeviceType());
final String deviceName = AudioSystem.getDeviceName(deviceType);
new MediaMetrics.Item(METRICS_DEVICE_PREFIX + deviceName)
- .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();
+ .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();
}
synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
loglogi("removeCompatibleAudioDevice: dev=" + ada);
- final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
- if (deviceState != null && deviceState.mEnabled) {
- deviceState.mEnabled = false;
+ final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ if (deviceState != null && deviceState.isSAEnabled()) {
+ deviceState.setSAEnabled(false);
onRoutingUpdated();
- mAudioService.persistSpatialAudioDeviceSettings();
+ mDeviceBroker.persistAudioDeviceSettings();
logDeviceState(deviceState, "removeCompatibleAudioDevice");
}
}
@@ -611,8 +623,9 @@ public class SpatializerHelper {
* Returns a possibly aliased device type which is used
* for spatial audio settings (or TYPE_UNKNOWN if it doesn't exist).
*/
- private static @AudioDeviceInfo.AudioDeviceType int getCanonicalDeviceType(int deviceType) {
- if (isWireless(deviceType)) return deviceType;
+ @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) {
@@ -629,18 +642,9 @@ public class SpatializerHelper {
*/
@GuardedBy("this")
@Nullable
- private SADeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada) {
- final int deviceType = ada.getType();
- final boolean isWireless = isWireless(deviceType);
- final int canonicalDeviceType = getCanonicalDeviceType(deviceType);
-
- for (SADeviceState deviceState : mSADevices) {
- if (deviceState.mDeviceType == canonicalDeviceType
- && (!isWireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
- return deviceState;
- }
- }
- return null;
+ private AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada) {
+ return mDeviceBroker.findDeviceStateForAudioDeviceAttributes(ada,
+ getCanonicalDeviceType(ada.getType(), ada.getInternalType()));
}
/**
@@ -662,14 +666,14 @@ public class SpatializerHelper {
Log.e(TAG, "no spatialization mode found for device type:" + deviceType);
return new Pair<>(false, false);
}
- final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ 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);
}
// found the matching device state.
- return new Pair<>(deviceState.mEnabled, true /* available */);
+ return new Pair<>(deviceState.isSAEnabled(), true /* available */);
}
private synchronized void addWirelessDeviceIfNew(@NonNull AudioDeviceAttributes ada) {
@@ -678,16 +682,19 @@ public class SpatializerHelper {
}
if (findDeviceStateForAudioDeviceAttributes(ada) == null) {
// wireless device types should be canonical, but we translate to be sure.
- final int canonicalDeviceType = getCanonicalDeviceType((ada.getType()));
+ final int canonicalDeviceType = getCanonicalDeviceType(ada.getType(),
+ ada.getInternalType());
if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) {
Log.e(TAG, "addWirelessDeviceIfNew with incompatible AudioDeviceAttributes "
+ ada);
return;
}
- final SADeviceState deviceState =
- new SADeviceState(canonicalDeviceType, ada.getAddress());
- mSADevices.add(deviceState);
- mAudioService.persistSpatialAudioDeviceSettings();
+ final AdiDeviceState deviceState =
+ new AdiDeviceState(canonicalDeviceType, ada.getInternalType(),
+ ada.getAddress());
+ initSAState(deviceState);
+ mDeviceBroker.addDeviceStateToInventory(deviceState);
+ mDeviceBroker.persistAudioDeviceSettings();
logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later.
}
}
@@ -756,6 +763,12 @@ public class SpatializerHelper {
return false;
}
+ private boolean isSADevice(AdiDeviceState deviceState) {
+ return deviceState.getDeviceType() == getCanonicalDeviceType(deviceState.getDeviceType(),
+ deviceState.getInternalDeviceType()) && isDeviceCompatibleWithSpatializationModes(
+ deviceState.getAudioDeviceAttributes());
+ }
+
synchronized void setFeatureEnabled(boolean enabled) {
loglogi("setFeatureEnabled(" + enabled + ") was featureEnabled:" + mFeatureEnabled);
if (mFeatureEnabled == enabled) {
@@ -768,7 +781,7 @@ public class SpatializerHelper {
return;
}
if (mState == STATE_UNINITIALIZED) {
- init(true, null /* settings */);
+ init(true);
}
setSpatializerEnabledInt(true);
} else {
@@ -1137,16 +1150,16 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, ignoring setHeadTrackerEnabled to " + enabled
+ " for " + ada);
}
- final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
if (deviceState == null) return;
- if (!deviceState.mHasHeadTracker) {
+ 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.mHeadTrackerEnabled = enabled;
- mAudioService.persistSpatialAudioDeviceSettings();
+ deviceState.setHeadTrackerEnabled(enabled);
+ mDeviceBroker.persistAudioDeviceSettings();
logDeviceState(deviceState, "setHeadTrackerEnabled");
// check current routing to see if it affects the headtracking mode
@@ -1170,8 +1183,8 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, hasHeadTracker always false for " + ada);
return false;
}
- final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
- return deviceState != null && deviceState.mHasHeadTracker;
+ final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ return deviceState != null && deviceState.hasHeadTracker();
}
/**
@@ -1184,14 +1197,14 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, setHasHeadTracker always false for " + ada);
return false;
}
- final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
if (deviceState != null) {
- if (!deviceState.mHasHeadTracker) {
- deviceState.mHasHeadTracker = true;
- mAudioService.persistSpatialAudioDeviceSettings();
+ if (!deviceState.hasHeadTracker()) {
+ deviceState.setHasHeadTracker(true);
+ mDeviceBroker.persistAudioDeviceSettings();
logDeviceState(deviceState, "setHasHeadTracker");
}
- return deviceState.mHeadTrackerEnabled;
+ return deviceState.isHeadTrackerEnabled();
}
Log.e(TAG, "setHasHeadTracker: device not found for:" + ada);
return false;
@@ -1202,9 +1215,9 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, isHeadTrackerEnabled always false for " + ada);
return false;
}
- final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
return deviceState != null
- && deviceState.mHasHeadTracker && deviceState.mHeadTrackerEnabled;
+ && deviceState.hasHeadTracker() && deviceState.isHeadTrackerEnabled();
}
synchronized boolean isHeadTrackerAvailable() {
@@ -1543,144 +1556,6 @@ 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 {
- private static boolean sBinauralEnabledDefault = true;
- private static boolean sTransauralEnabledDefault = true;
- private static boolean sHeadTrackingEnabledDefault = false;
- final @AudioDeviceInfo.AudioDeviceType int mDeviceType;
- final @NonNull String mDeviceAddress;
- boolean mEnabled;
- boolean mHasHeadTracker = false;
- boolean mHeadTrackerEnabled;
- static final String SETTING_FIELD_SEPARATOR = ",";
- static final String SETTING_DEVICE_SEPARATOR_CHAR = "|";
- static final String SETTING_DEVICE_SEPARATOR = "\\|";
-
- /**
- * Constructor
- * @param deviceType
- * @param address must be non-null for wireless devices
- * @throws NullPointerException if a null address is passed for a wireless device
- */
- SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @Nullable String address) {
- mDeviceType = deviceType;
- mDeviceAddress = isWireless(deviceType) ? Objects.requireNonNull(address) : "";
- final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
- mEnabled = spatMode == SpatializationMode.SPATIALIZER_BINAURAL
- ? sBinauralEnabledDefault
- : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL
- ? sTransauralEnabledDefault
- : false;
- mHeadTrackerEnabled = sHeadTrackingEnabledDefault;
- }
-
- @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;
- }
- }
-
- public AudioDeviceAttributes getAudioDeviceAttributes() {
- return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
- mDeviceType, mDeviceAddress == null ? "" : mDeviceAddress);
- }
-
- }
-
- /*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);
- // Note if the device is not compatible with spatialization mode
- // or the device type is not canonical, it is ignored.
- if (devState != null
- && devState.mDeviceType == getCanonicalDeviceType(devState.mDeviceType)
- && isDeviceCompatibleWithSpatializationModes(
- devState.getAudioDeviceAttributes())) {
- mSADevices.add(devState);
- logDeviceState(devState, "setSADeviceSettings");
- }
- }
}
private static String spatStateString(int state) {
@@ -1702,15 +1577,6 @@ public class SpatializerHelper {
}
}
- private static boolean isWireless(@AudioDeviceInfo.AudioDeviceType int deviceType) {
- for (int type : WIRELESS_TYPES) {
- if (type == deviceType) {
- return true;
- }
- }
- return false;
- }
-
private int getHeadSensorHandleUpdateTracker() {
int headHandle = -1;
if (sRoutingDevices.isEmpty()) {
@@ -1780,11 +1646,6 @@ public class SpatializerHelper {
//------------------------------------------------
// for testing purposes only
-
- /*package*/ void clearSADevices() {
- mSADevices.clear();
- }
-
/*package*/ synchronized void forceStateForTest(int state) {
mState = state;
}
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 31599eed539d..aba24fbd55b7 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -29,13 +29,14 @@ 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;
@@ -54,7 +55,6 @@ 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 {
- mContext = InstrumentationRegistry.getTargetContext();
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
mMockAudioService = mock(AudioService.class);
mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
mSpyDevInventory = spy(new AudioDeviceInventory(mSpyAudioSystem));
mSpySystemServer = spy(new NoOpSystemServerAdapter());
- mAudioDeviceBroker = new AudioDeviceBroker(mContext, mMockAudioService, mSpyDevInventory,
+ mAudioDeviceBroker = new AudioDeviceBroker(context, mMockAudioService, mSpyDevInventory,
mSpySystemServer, mSpyAudioSystem);
mSpyDevInventory.setDeviceBroker(mAudioDeviceBroker);
@@ -197,6 +197,37 @@ 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 3ad24de4cdca..ad09ef0ccdc1 100644
--- a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
@@ -15,8 +15,6 @@
*/
package com.android.server.audio;
-import com.android.server.audio.SpatializerHelper.SADeviceState;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.doNothing;
@@ -26,12 +24,12 @@ import static org.mockito.Mockito.when;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
-import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
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;
@@ -55,72 +53,25 @@ public class SpatializerHelperTest {
@Mock private AudioService mMockAudioService;
@Spy private AudioSystemAdapter mSpyAudioSystem;
- @Mock private AudioSystemAdapter mMockAudioSystem;
+ @Spy private AudioDeviceBroker mSpyDeviceBroker;
@Before
public void setUp() throws Exception {
mMockAudioService = mock(AudioService.class);
- }
-
- /**
- * Initializes mSpatHelper, the SpatizerHelper instance under test, to use the mock or spy
- * AudioSystemAdapter
- * @param useSpyAudioSystem true to use the spy adapter, mSpyAudioSystem, or false to use
- * the mock adapter, mMockAudioSystem.
- */
- private void setUpSpatHelper(boolean useSpyAudioSystem) {
- final AudioSystemAdapter asAdapter;
- if (useSpyAudioSystem) {
- mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
- asAdapter = mSpyAudioSystem;
- mMockAudioSystem = null;
- } else {
- mSpyAudioSystem = null;
- mMockAudioSystem = mock(NoOpAudioSystemAdapter.class);
- asAdapter = mMockAudioSystem;
- }
- mSpatHelper = new SpatializerHelper(mMockAudioService, asAdapter,
- true /*binauralEnabledDefault*/,
- true /*transauralEnabledDefault*/,
- false /*headTrackingEnabledDefault*/);
-
- }
- /**
- * Test that constructing an SADeviceState instance requires a non-null address for a
- * wireless type, but can take null for a non-wireless type;
- * @throws Exception
- */
- @Test
- public void testSADeviceStateNullAddressCtor() throws Exception {
- setUpSpatHelper(true /*useSpyAudioSystem*/);
- try {
- SADeviceState devState = new SADeviceState(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null);
- devState = new SADeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, null);
- Assert.fail();
- } catch (NullPointerException e) { }
- }
-
- @Test
- public void testSADeviceStateStringSerialization() throws Exception {
- Log.i(TAG, "starting testSADeviceStateStringSerialization");
- setUpSpatHelper(true /*useSpyAudioSystem*/);
- 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);
+ mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
+ mSpyDeviceBroker = spy(
+ new AudioDeviceBroker(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(),
+ mMockAudioService, mSpyAudioSystem));
+ mSpatHelper = new SpatializerHelper(mMockAudioService, mSpyAudioSystem,
+ mSpyDeviceBroker, /*binauralEnabledDefault=*/true, /*transauralEnabledDefault=*/
+ true, /*headTrackingEnabledDefault*/false);
}
@Test
- public void testSADeviceSettings() throws Exception {
+ public void testAdiDeviceStateSettings() throws Exception {
Log.i(TAG, "starting testSADeviceSettings");
- setUpSpatHelper(true /*useSpyAudioSystem*/);
final AudioDeviceAttributes dev1 =
new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, "");
final AudioDeviceAttributes dev2 =
@@ -128,7 +79,7 @@ public class SpatializerHelperTest {
final AudioDeviceAttributes dev3 =
new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "R2:D2:bloop");
- doNothing().when(mMockAudioService).persistSpatialAudioDeviceSettings();
+ doNothing().when(mSpyDeviceBroker).persistAudioDeviceSettings();
mSpatHelper.initForTest(true /*binaural*/, true /*transaural*/);
// test with single device
@@ -163,11 +114,11 @@ public class SpatializerHelperTest {
* the original one.
*/
private void checkAddSettings() throws Exception {
- String settings = mSpatHelper.getSADeviceSettings();
+ String settings = mSpyDeviceBroker.getDeviceSettings();
Log.i(TAG, "device settings: " + settings);
- mSpatHelper.clearSADevices();
- mSpatHelper.setSADeviceSettings(settings);
- String settingsRestored = mSpatHelper.getSADeviceSettings();
+ mSpyDeviceBroker.clearDeviceInventory();
+ mSpyDeviceBroker.setDeviceSettings(settings);
+ String settingsRestored = mSpyDeviceBroker.getDeviceSettings();
Log.i(TAG, "device settingsRestored: " + settingsRestored);
Assert.assertEquals(settings, settingsRestored);
}
@@ -179,7 +130,6 @@ public class SpatializerHelperTest {
@Test
public void testNoRoutingCanBeSpatialized() throws Exception {
Log.i(TAG, "Starting testNoRoutingCanBeSpatialized");
- setUpSpatHelper(false /*useSpyAudioSystem*/);
mSpatHelper.forceStateForTest(SpatializerHelper.STATE_ENABLED_AVAILABLE);
final ArrayList<AudioDeviceAttributes> emptyList = new ArrayList<>(0);
@@ -191,12 +141,12 @@ public class SpatializerHelperTest {
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1).build();
- when(mMockAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
+ when(mSpyAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
.thenReturn(emptyList);
Assert.assertFalse("can be spatialized on empty routing",
mSpatHelper.canBeSpatialized(media, spatialFormat));
- when(mMockAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
+ when(mSpyAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
.thenReturn(listWithNull);
Assert.assertFalse("can be spatialized on null routing",
mSpatHelper.canBeSpatialized(media, spatialFormat));