diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1fa41eb41b7..92157f3e9b1 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -156,6 +156,8 @@
Shortcut, hearing aid compatibility
Presets
+
+ Couldn\u2019t update preset
Audio output
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java
index 208f8d0afbd..43721f4cf92 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java
@@ -19,15 +19,18 @@ package com.android.settings.bluetooth;
import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP;
import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.ORDER_HEARING_AIDS_PRESETS;
+import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHapClient;
import android.bluetooth.BluetoothHapPresetInfo;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
@@ -88,8 +91,53 @@ public class BluetoothDetailsHearingAidsPresetsController extends
@Override
public boolean onPreferenceChange(@NonNull Preference preference, @Nullable Object newValue) {
if (TextUtils.equals(preference.getKey(), getPreferenceKey())) {
- // TODO(b/300015207): Update the settings to remote device
- return true;
+ if (newValue instanceof final String value
+ && preference instanceof final ListPreference listPreference) {
+ final int index = listPreference.findIndexOfValue(value);
+ final String presetName = listPreference.getEntries()[index].toString();
+ final int presetIndex = Integer.parseInt(
+ listPreference.getEntryValues()[index].toString());
+ listPreference.setSummary(presetName);
+ boolean supportSynchronizedPresets = mHapClientProfile.supportsSynchronizedPresets(
+ mCachedDevice.getDevice());
+ int hapGroupId = mHapClientProfile.getHapGroup(mCachedDevice.getDevice());
+ if (supportSynchronizedPresets
+ && hapGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ if (DEBUG) {
+ Log.d(TAG, "onPreferenceChange, selectPresetForGroup "
+ + ", presetName: " + presetName
+ + ", presetIndex: " + presetIndex
+ + ", hapGroupId: " + hapGroupId
+ + ", device: " + mCachedDevice.getAddress());
+ }
+ mHapClientProfile.selectPresetForGroup(hapGroupId, presetIndex);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "onPreferenceChange, selectPreset "
+ + ", presetName: " + presetName
+ + ", presetIndex: " + presetIndex
+ + ", device: " + mCachedDevice.getAddress());
+ }
+ mHapClientProfile.selectPreset(mCachedDevice.getDevice(), presetIndex);
+ final CachedBluetoothDevice subDevice = mCachedDevice.getSubDevice();
+ if (subDevice != null) {
+ if (DEBUG) {
+ Log.d(TAG, "onPreferenceChange, selectPreset for subDevice"
+ + ", device: " + subDevice.getAddress());
+ }
+ mHapClientProfile.selectPreset(subDevice.getDevice(), presetIndex);
+ }
+ for (final CachedBluetoothDevice memberDevice :
+ mCachedDevice.getMemberDevice()) {
+ if (DEBUG) {
+ Log.d(TAG, "onPreferenceChange, selectPreset for memberDevice"
+ + ", device: " + memberDevice.getAddress());
+ }
+ mHapClientProfile.selectPreset(memberDevice.getDevice(), presetIndex);
+ }
+ }
+ return true;
+ }
}
return false;
}
@@ -115,12 +163,29 @@ public class BluetoothDetailsHearingAidsPresetsController extends
return;
}
mPreference.setEnabled(mCachedDevice.isConnectedHapClientDevice());
- // TODO(b/300015207): Load preset from remote and show in UI
+
+ loadAllPresetInfo();
+ if (mPreference.getEntries().length == 0) {
+ mPreference.setEnabled(false);
+ } else {
+ int activePresetIndex = mHapClientProfile.getActivePresetIndex(
+ mCachedDevice.getDevice());
+ if (activePresetIndex != BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) {
+ mPreference.setValue(Integer.toString(activePresetIndex));
+ mPreference.setSummary(mPreference.getEntry());
+ } else {
+ mPreference.setSummary(null);
+ }
+ }
}
@Override
public boolean isAvailable() {
- return false;
+ if (mHapClientProfile == null) {
+ return false;
+ }
+ return mCachedDevice.getProfiles().stream().anyMatch(
+ profile -> profile instanceof HapClientProfile);
}
@Override
@@ -130,7 +195,7 @@ public class BluetoothDetailsHearingAidsPresetsController extends
Log.d(TAG, "onPresetSelected, device: " + device.getAddress()
+ ", presetIndex: " + presetIndex + ", reason: " + reason);
}
- // TODO(b/300015207): Update the UI
+ mContext.getMainExecutor().execute(this::refresh);
}
}
@@ -142,7 +207,10 @@ public class BluetoothDetailsHearingAidsPresetsController extends
"onPresetSelectionFailed, device: " + device.getAddress()
+ ", reason: " + reason);
}
- // TODO(b/300015207): Update the UI
+ mContext.getMainExecutor().execute(() -> {
+ refresh();
+ showErrorToast();
+ });
}
}
@@ -153,7 +221,10 @@ public class BluetoothDetailsHearingAidsPresetsController extends
Log.d(TAG, "onPresetSelectionForGroupFailed, group: " + hapGroupId
+ ", reason: " + reason);
}
- // TODO(b/300015207): Update the UI
+ mContext.getMainExecutor().execute(() -> {
+ refresh();
+ showErrorToast();
+ });
}
}
@@ -166,7 +237,7 @@ public class BluetoothDetailsHearingAidsPresetsController extends
+ ", reason: " + reason
+ ", infoList: " + presetInfoList);
}
- // TODO(b/300015207): Update the UI
+ mContext.getMainExecutor().execute(this::refresh);
}
}
@@ -178,7 +249,10 @@ public class BluetoothDetailsHearingAidsPresetsController extends
"onSetPresetNameFailed, device: " + device.getAddress()
+ ", reason: " + reason);
}
- // TODO(b/300015207): Update the UI
+ mContext.getMainExecutor().execute(() -> {
+ refresh();
+ showErrorToast();
+ });
}
}
@@ -189,7 +263,10 @@ public class BluetoothDetailsHearingAidsPresetsController extends
Log.d(TAG, "onSetPresetNameForGroupFailed, group: " + hapGroupId
+ ", reason: " + reason);
}
- // TODO(b/300015207): Update the UI
+ mContext.getMainExecutor().execute(() -> {
+ refresh();
+ showErrorToast();
+ });
}
}
@@ -201,4 +278,31 @@ public class BluetoothDetailsHearingAidsPresetsController extends
preference.setOnPreferenceChangeListener(this);
return preference;
}
+
+ private void loadAllPresetInfo() {
+ if (mPreference == null) {
+ return;
+ }
+ List infoList = mHapClientProfile.getAllPresetInfo(
+ mCachedDevice.getDevice());
+ CharSequence[] presetNames = new CharSequence[infoList.size()];
+ CharSequence[] presetIndexes = new CharSequence[infoList.size()];
+ for (int i = 0; i < infoList.size(); i++) {
+ presetNames[i] = infoList.get(i).getName();
+ presetIndexes[i] = Integer.toString(infoList.get(i).getIndex());
+ }
+ mPreference.setEntries(presetNames);
+ mPreference.setEntryValues(presetIndexes);
+ }
+
+ @VisibleForTesting
+ @Nullable
+ ListPreference getPreference() {
+ return mPreference;
+ }
+
+ void showErrorToast() {
+ Toast.makeText(mContext, R.string.bluetooth_hearing_aids_presets_error,
+ Toast.LENGTH_SHORT).show();
+ }
}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsControllerTest.java
index 3a898c150e1..c08bb98e55b 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsControllerTest.java
@@ -16,21 +16,29 @@
package com.android.settings.bluetooth;
+import static android.bluetooth.BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+import static android.bluetooth.BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
+
import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP;
import static com.android.settings.bluetooth.BluetoothDetailsHearingAidsPresetsController.KEY_HEARING_AIDS_PRESETS;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothHapPresetInfo;
import androidx.preference.ListPreference;
import androidx.preference.PreferenceCategory;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HapClientProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
@@ -43,7 +51,9 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.Executor;
/** Tests for {@link BluetoothDetailsHearingAidsPresetsController}. */
@@ -53,6 +63,7 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
private static final int TEST_PRESET_INDEX = 1;
private static final String TEST_PRESET_NAME = "test_preset";
+ private static final int TEST_HAP_GROUP_ID = 1;
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -63,6 +74,10 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
private LocalBluetoothProfileManager mProfileManager;
@Mock
private HapClientProfile mHapClientProfile;
+ @Mock
+ private CachedBluetoothDevice mCachedChildDevice;
+ @Mock
+ private BluetoothDevice mChildDevice;
private BluetoothDetailsHearingAidsPresetsController mController;
@@ -73,6 +88,8 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
when(mLocalManager.getProfileManager()).thenReturn(mProfileManager);
when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile));
+ when(mCachedDevice.isConnectedHapClientDevice()).thenReturn(true);
+ when(mCachedChildDevice.getDevice()).thenReturn(mChildDevice);
PreferenceCategory deviceControls = new PreferenceCategory(mContext);
deviceControls.setKey(KEY_HEARING_DEVICE_GROUP);
mScreen.addPreference(deviceControls);
@@ -81,6 +98,20 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
mController.init(mScreen);
}
+ @Test
+ public void isAvailable_supportHap_returnTrue() {
+ when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile));
+
+ assertThat(mController.isAvailable()).isTrue();
+ }
+
+ @Test
+ public void isAvailable_notSupportHap_returnFalse() {
+ when(mCachedDevice.getProfiles()).thenReturn(new ArrayList<>());
+
+ assertThat(mController.isAvailable()).isFalse();
+ }
+
@Test
public void onResume_registerCallback() {
mController.onResume();
@@ -96,7 +127,6 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
verify(mHapClientProfile).unregisterCallback(any(BluetoothHapClient.Callback.class));
}
-
@Test
public void onPreferenceChange_keyMatched_verifyStatusUpdated() {
final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS);
@@ -105,6 +135,7 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
String.valueOf(TEST_PRESET_INDEX));
assertThat(handled).isTrue();
+ verify(presetPreference).setSummary(TEST_PRESET_NAME);
}
@Test
@@ -115,6 +146,116 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
presetPreference, String.valueOf(TEST_PRESET_INDEX));
assertThat(handled).isFalse();
+ verify(presetPreference, never()).setSummary(any());
+ }
+
+ @Test
+ public void onPreferenceChange_supportGroupOperation_validGroupId_verifySelectPresetForGroup() {
+ final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS);
+ when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(true);
+ when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(TEST_HAP_GROUP_ID);
+
+ mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX));
+
+ verify(mHapClientProfile).selectPresetForGroup(TEST_HAP_GROUP_ID, TEST_PRESET_INDEX);
+ }
+
+ @Test
+ public void onPreferenceChange_notSupportGroupOperation_verifySelectPreset() {
+ final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS);
+ when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(false);
+ when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(TEST_HAP_GROUP_ID);
+
+ mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX));
+
+ verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
+ }
+
+ @Test
+ public void onPreferenceChange_invalidGroupId_verifySelectPreset() {
+ final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS);
+ when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(true);
+ when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(GROUP_ID_INVALID);
+
+ mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX));
+
+ verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
+ }
+
+ @Test
+ public void onPreferenceChange_notSupportGroupOperation_hasSubDevice_verifyStatusUpdated() {
+ final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS);
+ when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(false);
+ when(mCachedDevice.getSubDevice()).thenReturn(mCachedChildDevice);
+
+ mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX));
+
+ verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mChildDevice, TEST_PRESET_INDEX);
+ }
+
+ @Test
+ public void onPreferenceChange_notSupportGroupOperation_hasMemberDevice_verifyStatusUpdated() {
+ final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS);
+ when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(false);
+ when(mCachedDevice.getMemberDevice()).thenReturn(Set.of(mCachedChildDevice));
+
+ mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX));
+
+ verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mChildDevice, TEST_PRESET_INDEX);
+ }
+
+ @Test
+ public void refresh_emptyPresetInfo_preferenceDisabled() {
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(new ArrayList<>());
+
+ mController.refresh();
+
+ assertThat(mController.getPreference()).isNotNull();
+ assertThat(mController.getPreference().isEnabled()).isFalse();
+ }
+
+ @Test
+ public void refresh_validPresetInfo_preferenceEnabled() {
+ BluetoothHapPresetInfo info = getTestPresetInfo();
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
+
+ mController.refresh();
+
+ assertThat(mController.getPreference()).isNotNull();
+ assertThat(mController.getPreference().isEnabled()).isTrue();
+ }
+
+ @Test
+ public void refresh_invalidActivePresetIndex_summaryIsNull() {
+ BluetoothHapPresetInfo info = getTestPresetInfo();
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
+ when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(PRESET_INDEX_UNAVAILABLE);
+
+ mController.refresh();
+
+ assertThat(mController.getPreference()).isNotNull();
+ assertThat(mController.getPreference().getSummary()).isNull();
+ }
+
+ @Test
+ public void refresh_validActivePresetIndex_summaryIsNotNull() {
+ BluetoothHapPresetInfo info = getTestPresetInfo();
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
+ when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX);
+
+ mController.refresh();
+
+ assertThat(mController.getPreference()).isNotNull();
+ assertThat(mController.getPreference().getSummary()).isNotNull();
+ }
+
+ private BluetoothHapPresetInfo getTestPresetInfo() {
+ BluetoothHapPresetInfo info = mock(BluetoothHapPresetInfo.class);
+ when(info.getName()).thenReturn(TEST_PRESET_NAME);
+ when(info.getIndex()).thenReturn(TEST_PRESET_INDEX);
+ return info;
}
private ListPreference getTestPresetPreference(String key) {