Merge "Implement Spatial audio toggle domain layer" into main
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index de60fdc2..b3ac54a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -21,6 +21,8 @@
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.net.Uri;
import android.provider.DeviceConfig;
@@ -41,9 +43,12 @@
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.settingslib.widget.AdaptiveOutlineDrawable;
+import com.google.common.collect.ImmutableSet;
+
import java.io.IOException;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -57,6 +62,9 @@
public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled";
private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
+ private static final Set<Integer> SA_PROFILES =
+ ImmutableSet.of(
+ BluetoothProfile.A2DP, BluetoothProfile.LE_AUDIO, BluetoothProfile.HEARING_AID);
private static ErrorListener sErrorListener;
@@ -895,4 +903,62 @@
}
return null;
}
+
+ /**
+ * Gets {@link AudioDeviceAttributes} of bluetooth device for spatial audio. Returns null if
+ * it's not an audio device(no A2DP, LE Audio and Hearing Aid profile).
+ */
+ @Nullable
+ public static AudioDeviceAttributes getAudioDeviceAttributesForSpatialAudio(
+ CachedBluetoothDevice cachedDevice,
+ @AudioManager.AudioDeviceCategory int audioDeviceCategory) {
+ AudioDeviceAttributes saDevice = null;
+ for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) {
+ // pick first enabled profile that is compatible with spatial audio
+ if (SA_PROFILES.contains(profile.getProfileId())
+ && profile.isEnabled(cachedDevice.getDevice())) {
+ switch (profile.getProfileId()) {
+ case BluetoothProfile.A2DP:
+ saDevice =
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+ cachedDevice.getAddress());
+ break;
+ case BluetoothProfile.LE_AUDIO:
+ if (audioDeviceCategory
+ == AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER) {
+ saDevice =
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_SPEAKER,
+ cachedDevice.getAddress());
+ } else {
+ saDevice =
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ cachedDevice.getAddress());
+ }
+
+ break;
+ case BluetoothProfile.HEARING_AID:
+ saDevice =
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_HEARING_AID,
+ cachedDevice.getAddress());
+ break;
+ default:
+ Log.i(
+ TAG,
+ "unrecognized profile for spatial audio: "
+ + profile.getProfileId());
+ break;
+ }
+ break;
+ }
+ }
+ return saDevice;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
index 20a0339..58dc8c7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
@@ -108,6 +108,12 @@
/** Device setting ID for device details footer. */
int DEVICE_SETTING_ID_DEVICE_DETAILS_FOOTER = 19;
+ /** Device setting ID for spatial audio group. */
+ int DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE = 20;
+
+ /** Device setting ID for "More Settings" page. */
+ int DEVICE_SETTING_ID_MORE_SETTINGS = 21;
+
/** Device setting ID for ANC. */
int DEVICE_SETTING_ID_ANC = 1001;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
index a599dd1..ce7064c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
@@ -29,6 +29,7 @@
import com.android.settingslib.bluetooth.devicesettings.ToggleInfo
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
@@ -117,7 +118,7 @@
id = settingId,
title = pref.title,
summary = pref.summary,
- icon = pref.icon,
+ icon = pref.icon?.let { DeviceSettingIcon.BitmapIcon(it) },
isAllowedChangingState = pref.isAllowedChangingState,
intent = pref.intent,
switchState =
@@ -153,5 +154,6 @@
else -> DeviceSettingModel.Unknown(cachedDevice, settingId)
}
- private fun ToggleInfo.toModel(): ToggleModel = ToggleModel(label, icon)
+ private fun ToggleInfo.toModel(): ToggleModel =
+ ToggleModel(label, DeviceSettingIcon.BitmapIcon(icon))
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
index 136abad..e97f76c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
@@ -35,7 +35,7 @@
/** A built-in item in Settings. */
data class BuiltinItem(
@DeviceSettingId override val settingId: Int,
- val preferenceKey: String
+ val preferenceKey: String?
) : DeviceSettingConfigItemModel
/** A remote item provided by other apps. */
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt
index db78280..2a63217 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt
@@ -18,6 +18,7 @@
import android.content.Intent
import android.graphics.Bitmap
+import androidx.annotation.DrawableRes
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
@@ -32,7 +33,7 @@
@DeviceSettingId override val id: Int,
val title: String,
val summary: String? = null,
- val icon: Bitmap? = null,
+ val icon: DeviceSettingIcon? = null,
val intent: Intent? = null,
val switchState: DeviceSettingStateModel.ActionSwitchPreferenceState? = null,
val isAllowedChangingState: Boolean = true,
@@ -59,4 +60,12 @@
}
/** Models a toggle in [DeviceSettingModel.MultiTogglePreference]. */
-data class ToggleModel(val label: String, val icon: Bitmap)
+data class ToggleModel(val label: String, val icon: DeviceSettingIcon)
+
+/** Models an icon in device settings. */
+sealed interface DeviceSettingIcon {
+
+ data class BitmapIcon(val bitmap: Bitmap) : DeviceSettingIcon
+
+ data class ResourceIcon(@DrawableRes val resId: Int) : DeviceSettingIcon
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 4551f1e..926d3cb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -31,10 +31,13 @@
import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.net.Uri;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -70,6 +73,9 @@
@Mock private BluetoothDevice mBluetoothDevice;
@Mock private AudioManager mAudioManager;
@Mock private PackageManager mPackageManager;
+ @Mock private LeAudioProfile mA2dpProfile;
+ @Mock private LeAudioProfile mLeAudioProfile;
+ @Mock private LeAudioProfile mHearingAid;
@Mock private LocalBluetoothLeBroadcast mBroadcast;
@Mock private LocalBluetoothProfileManager mProfileManager;
@Mock private LocalBluetoothManager mLocalBluetoothManager;
@@ -100,6 +106,9 @@
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
+ when(mA2dpProfile.getProfileId()).thenReturn(BluetoothProfile.A2DP);
+ when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO);
+ when(mHearingAid.getProfileId()).thenReturn(BluetoothProfile.HEARING_AID);
}
@Test
@@ -756,4 +765,84 @@
mContext.getContentResolver(), mLocalBluetoothManager))
.isEqualTo(mCachedBluetoothDevice);
}
+
+ @Test
+ public void getAudioDeviceAttributesForSpatialAudio_bleHeadset() {
+ String address = "11:22:33:44:55:66";
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+ when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mLeAudioProfile));
+ when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(true);
+
+ AudioDeviceAttributes attr =
+ BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+ mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES);
+
+ assertThat(attr)
+ .isEqualTo(
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ address));
+ }
+
+ @Test
+ public void getAudioDeviceAttributesForSpatialAudio_bleSpeaker() {
+ String address = "11:22:33:44:55:66";
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+ when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mLeAudioProfile));
+ when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(true);
+
+ AudioDeviceAttributes attr =
+ BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+ mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER);
+
+ assertThat(attr)
+ .isEqualTo(
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_SPEAKER,
+ address));
+ }
+
+ @Test
+ public void getAudioDeviceAttributesForSpatialAudio_a2dp() {
+ String address = "11:22:33:44:55:66";
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+ when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mA2dpProfile));
+ when(mA2dpProfile.isEnabled(mBluetoothDevice)).thenReturn(true);
+
+ AudioDeviceAttributes attr =
+ BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+ mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES);
+
+ assertThat(attr)
+ .isEqualTo(
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+ address));
+ }
+
+ @Test
+ public void getAudioDeviceAttributesForSpatialAudio_hearingAid() {
+ String address = "11:22:33:44:55:66";
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+ when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mHearingAid));
+ when(mHearingAid.isEnabled(mBluetoothDevice)).thenReturn(true);
+
+ AudioDeviceAttributes attr =
+ BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+ mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_HEARING_AID);
+
+ assertThat(attr)
+ .isEqualTo(
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_HEARING_AID,
+ address));
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
index fee2394..4c5ee9e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
@@ -40,6 +40,7 @@
import com.android.settingslib.bluetooth.devicesettings.ToggleInfo
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
@@ -352,7 +353,7 @@
val pref = serviceResponse.preference as ActionSwitchPreference
assertThat(actual.title).isEqualTo(pref.title)
assertThat(actual.summary).isEqualTo(pref.summary)
- assertThat(actual.icon).isEqualTo(pref.icon)
+ assertThat(actual.icon).isEqualTo(DeviceSettingIcon.BitmapIcon(pref.icon!!))
assertThat(actual.isAllowedChangingState).isEqualTo(pref.isAllowedChangingState)
if (pref.hasSwitch()) {
assertThat(actual.switchState!!.checked).isEqualTo(pref.checked)