Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b054b05b authored by Angela Wang's avatar Angela Wang
Browse files

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
parent 82e4ed3b
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -156,6 +156,8 @@
    <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] -->
    <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] -->
    <string name="bluetooth_audio_routing_title">Audio output</string>
    <!-- Title for bluetooth audio routing page footer. [CHAR LIMIT=30] -->
+114 −10
Original line number Diff line number Diff line
@@ -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,9 +91,54 @@ 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
            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,13 +163,30 @@ 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() {
        if (mHapClientProfile == null) {
            return false;
        }
        return mCachedDevice.getProfiles().stream().anyMatch(
                profile -> profile instanceof HapClientProfile);
    }

    @Override
    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()
                        + ", 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<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();
    }
}
+142 −1
Original line number Diff line number Diff line
@@ -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) {