Selects presets in device details page (2/2)

Updates UI and sets preset to remote device when corresponding callback is called.

Bug: 300015207
Test: atest BluetoothDetailsHearingAidsPresetsControllerTest
Change-Id: Ic013b96acaa6161b861fbae32ddfd77387f9bc47
This commit is contained in:
Angela Wang
2024-01-22 07:11:27 +00:00
parent 82e4ed3bd1
commit b054b05b78
3 changed files with 258 additions and 11 deletions

View File

@@ -156,6 +156,8 @@
<string name="bluetooth_hearing_device_settings_summary">Shortcut, hearing aid compatibility</string> <string name="bluetooth_hearing_device_settings_summary">Shortcut, hearing aid compatibility</string>
<!-- Connected devices settings. Title for hearing aids presets. A preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=60] --> <!-- Connected devices settings. Title for hearing aids presets. A preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=60] -->
<string name="bluetooth_hearing_aids_presets">Presets</string> <string name="bluetooth_hearing_aids_presets">Presets</string>
<!-- Message when selecting hearing aids presets failed. [CHAR LIMIT=NONE] -->
<string name="bluetooth_hearing_aids_presets_error">Couldn\u2019t update preset</string>
<!-- Connected devices settings. Title of the preference to show the entrance of the audio output page. It can change different types of audio are played on phone or other bluetooth devices. [CHAR LIMIT=35] --> <!-- Connected devices settings. Title of the preference to show the entrance of the audio output page. It can change different types of audio are played on phone or other bluetooth devices. [CHAR LIMIT=35] -->
<string name="bluetooth_audio_routing_title">Audio output</string> <string name="bluetooth_audio_routing_title">Audio output</string>
<!-- Title for bluetooth audio routing page footer. [CHAR LIMIT=30] --> <!-- Title for bluetooth audio routing page footer. [CHAR LIMIT=30] -->

View File

@@ -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.KEY_HEARING_DEVICE_GROUP;
import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.ORDER_HEARING_AIDS_PRESETS; import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.ORDER_HEARING_AIDS_PRESETS;
import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHapClient; import android.bluetooth.BluetoothHapClient;
import android.bluetooth.BluetoothHapPresetInfo; import android.bluetooth.BluetoothHapPresetInfo;
import android.content.Context; import android.content.Context;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.ListPreference; import androidx.preference.ListPreference;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceCategory;
@@ -88,9 +91,54 @@ public class BluetoothDetailsHearingAidsPresetsController extends
@Override @Override
public boolean onPreferenceChange(@NonNull Preference preference, @Nullable Object newValue) { public boolean onPreferenceChange(@NonNull Preference preference, @Nullable Object newValue) {
if (TextUtils.equals(preference.getKey(), getPreferenceKey())) { if (TextUtils.equals(preference.getKey(), getPreferenceKey())) {
// TODO(b/300015207): Update the settings to remote device 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 true;
} }
}
return false; return false;
} }
@@ -115,13 +163,30 @@ public class BluetoothDetailsHearingAidsPresetsController extends
return; return;
} }
mPreference.setEnabled(mCachedDevice.isConnectedHapClientDevice()); 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 @Override
public boolean isAvailable() { public boolean isAvailable() {
if (mHapClientProfile == null) {
return false; return false;
} }
return mCachedDevice.getProfiles().stream().anyMatch(
profile -> profile instanceof HapClientProfile);
}
@Override @Override
public void onPresetSelected(@NonNull BluetoothDevice device, int presetIndex, int reason) { public void onPresetSelected(@NonNull BluetoothDevice device, int presetIndex, int reason) {
@@ -130,7 +195,7 @@ public class BluetoothDetailsHearingAidsPresetsController extends
Log.d(TAG, "onPresetSelected, device: " + device.getAddress() Log.d(TAG, "onPresetSelected, device: " + device.getAddress()
+ ", presetIndex: " + presetIndex + ", reason: " + reason); + ", 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() "onPresetSelectionFailed, device: " + device.getAddress()
+ ", reason: " + reason); + ", 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 Log.d(TAG, "onPresetSelectionForGroupFailed, group: " + hapGroupId
+ ", reason: " + reason); + ", reason: " + reason);
} }
// TODO(b/300015207): Update the UI mContext.getMainExecutor().execute(() -> {
refresh();
showErrorToast();
});
} }
} }
@@ -166,7 +237,7 @@ public class BluetoothDetailsHearingAidsPresetsController extends
+ ", reason: " + reason + ", reason: " + reason
+ ", infoList: " + presetInfoList); + ", 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() "onSetPresetNameFailed, device: " + device.getAddress()
+ ", reason: " + reason); + ", 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 Log.d(TAG, "onSetPresetNameForGroupFailed, group: " + hapGroupId
+ ", reason: " + reason); + ", reason: " + reason);
} }
// TODO(b/300015207): Update the UI mContext.getMainExecutor().execute(() -> {
refresh();
showErrorToast();
});
} }
} }
@@ -201,4 +278,31 @@ public class BluetoothDetailsHearingAidsPresetsController extends
preference.setOnPreferenceChangeListener(this); preference.setOnPreferenceChangeListener(this);
return preference; return preference;
} }
private void loadAllPresetInfo() {
if (mPreference == null) {
return;
}
List<BluetoothHapPresetInfo> 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();
}
} }

View File

@@ -16,21 +16,29 @@
package com.android.settings.bluetooth; 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.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP;
import static com.android.settings.bluetooth.BluetoothDetailsHearingAidsPresetsController.KEY_HEARING_AIDS_PRESETS; import static com.android.settings.bluetooth.BluetoothDetailsHearingAidsPresetsController.KEY_HEARING_AIDS_PRESETS;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any; 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.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHapClient; import android.bluetooth.BluetoothHapClient;
import android.bluetooth.BluetoothHapPresetInfo;
import androidx.preference.ListPreference; import androidx.preference.ListPreference;
import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceCategory;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HapClientProfile; import com.android.settingslib.bluetooth.HapClientProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
@@ -43,7 +51,9 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule; import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
/** Tests for {@link BluetoothDetailsHearingAidsPresetsController}. */ /** Tests for {@link BluetoothDetailsHearingAidsPresetsController}. */
@@ -53,6 +63,7 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
private static final int TEST_PRESET_INDEX = 1; private static final int TEST_PRESET_INDEX = 1;
private static final String TEST_PRESET_NAME = "test_preset"; private static final String TEST_PRESET_NAME = "test_preset";
private static final int TEST_HAP_GROUP_ID = 1;
@Rule @Rule
public final MockitoRule mockito = MockitoJUnit.rule(); public final MockitoRule mockito = MockitoJUnit.rule();
@@ -63,6 +74,10 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
private LocalBluetoothProfileManager mProfileManager; private LocalBluetoothProfileManager mProfileManager;
@Mock @Mock
private HapClientProfile mHapClientProfile; private HapClientProfile mHapClientProfile;
@Mock
private CachedBluetoothDevice mCachedChildDevice;
@Mock
private BluetoothDevice mChildDevice;
private BluetoothDetailsHearingAidsPresetsController mController; private BluetoothDetailsHearingAidsPresetsController mController;
@@ -73,6 +88,8 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
when(mLocalManager.getProfileManager()).thenReturn(mProfileManager); when(mLocalManager.getProfileManager()).thenReturn(mProfileManager);
when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile); when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile)); when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile));
when(mCachedDevice.isConnectedHapClientDevice()).thenReturn(true);
when(mCachedChildDevice.getDevice()).thenReturn(mChildDevice);
PreferenceCategory deviceControls = new PreferenceCategory(mContext); PreferenceCategory deviceControls = new PreferenceCategory(mContext);
deviceControls.setKey(KEY_HEARING_DEVICE_GROUP); deviceControls.setKey(KEY_HEARING_DEVICE_GROUP);
mScreen.addPreference(deviceControls); mScreen.addPreference(deviceControls);
@@ -81,6 +98,20 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
mController.init(mScreen); 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 @Test
public void onResume_registerCallback() { public void onResume_registerCallback() {
mController.onResume(); mController.onResume();
@@ -96,7 +127,6 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
verify(mHapClientProfile).unregisterCallback(any(BluetoothHapClient.Callback.class)); verify(mHapClientProfile).unregisterCallback(any(BluetoothHapClient.Callback.class));
} }
@Test @Test
public void onPreferenceChange_keyMatched_verifyStatusUpdated() { public void onPreferenceChange_keyMatched_verifyStatusUpdated() {
final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS); final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS);
@@ -105,6 +135,7 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
String.valueOf(TEST_PRESET_INDEX)); String.valueOf(TEST_PRESET_INDEX));
assertThat(handled).isTrue(); assertThat(handled).isTrue();
verify(presetPreference).setSummary(TEST_PRESET_NAME);
} }
@Test @Test
@@ -115,6 +146,116 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
presetPreference, String.valueOf(TEST_PRESET_INDEX)); presetPreference, String.valueOf(TEST_PRESET_INDEX));
assertThat(handled).isFalse(); 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) { private ListPreference getTestPresetPreference(String key) {