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

Commit f0dc329a authored by Jason Hsu's avatar Jason Hsu Committed by Android (Google) Code Review
Browse files

Merge "Fix crash in Hearing Devices dialog when connecting to ASHA device with...

Merge "Fix crash in Hearing Devices dialog when connecting to ASHA device with LE Audio disabled" into main
parents 026dffd6 a1477b70
Loading
Loading
Loading
Loading
+2 −2
Original line number Original line Diff line number Diff line
@@ -228,7 +228,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
        mHearingDeviceItemList = getHearingDevicesList();
        mHearingDeviceItemList = getHearingDevicesList();
        if (mPresetsController != null) {
        if (mPresetsController != null) {
            activeHearingDevice = getActiveHearingDevice(mHearingDeviceItemList);
            activeHearingDevice = getActiveHearingDevice(mHearingDeviceItemList);
            mPresetsController.setActiveHearingDevice(activeHearingDevice);
            mPresetsController.setHearingDeviceIfSupportHap(activeHearingDevice);
        } else {
        } else {
            activeHearingDevice = null;
            activeHearingDevice = null;
        }
        }
@@ -336,7 +336,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
        }
        }
        final CachedBluetoothDevice activeHearingDevice = getActiveHearingDevice(
        final CachedBluetoothDevice activeHearingDevice = getActiveHearingDevice(
                mHearingDeviceItemList);
                mHearingDeviceItemList);
        mPresetsController.setActiveHearingDevice(activeHearingDevice);
        mPresetsController.setHearingDeviceIfSupportHap(activeHearingDevice);


        mPresetInfoAdapter = new ArrayAdapter<>(dialog.getContext(),
        mPresetInfoAdapter = new ArrayAdapter<>(dialog.getContext(),
                R.layout.hearing_devices_preset_spinner_selected,
                R.layout.hearing_devices_preset_spinner_selected,
+22 −11
Original line number Original line Diff line number Diff line
@@ -113,7 +113,7 @@ public class HearingDevicesPresetsController implements


    @Override
    @Override
    public void onPresetSelectionForGroupFailed(int hapGroupId, int reason) {
    public void onPresetSelectionForGroupFailed(int hapGroupId, int reason) {
        if (mActiveHearingDevice == null) {
        if (mActiveHearingDevice == null || mHapClientProfile == null) {
            return;
            return;
        }
        }
        if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) {
        if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) {
@@ -137,7 +137,7 @@ public class HearingDevicesPresetsController implements


    @Override
    @Override
    public void onSetPresetNameForGroupFailed(int hapGroupId, int reason) {
    public void onSetPresetNameForGroupFailed(int hapGroupId, int reason) {
        if (mActiveHearingDevice == null) {
        if (mActiveHearingDevice == null || mHapClientProfile == null) {
            return;
            return;
        }
        }
        if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) {
        if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) {
@@ -177,22 +177,33 @@ public class HearingDevicesPresetsController implements
    }
    }


    /**
    /**
     * Sets the hearing device for this controller to control the preset.
     * Sets the hearing device for this controller to control the preset if it supports
     * {@link HapClientProfile}.
     *
     *
     * @param activeHearingDevice the {@link CachedBluetoothDevice} need to be hearing aid device
     * @param activeHearingDevice the {@link CachedBluetoothDevice} need to be hearing aid device
     *                            and support {@link HapClientProfile}.
     */
     */
    public void setActiveHearingDevice(CachedBluetoothDevice activeHearingDevice) {
    public void setHearingDeviceIfSupportHap(CachedBluetoothDevice activeHearingDevice) {
        if (mHapClientProfile == null || activeHearingDevice == null) {
            mActiveHearingDevice = null;
            return;
        }
        if (activeHearingDevice.getProfiles().stream().anyMatch(
                profile -> profile instanceof HapClientProfile)) {
            mActiveHearingDevice = activeHearingDevice;
            mActiveHearingDevice = activeHearingDevice;
        } else {
            mActiveHearingDevice = null;
        }
    }
    }


    /**
    /**
     * Selects the currently active preset for {@code mActiveHearingDevice} individual device or
     * Selects the currently active preset for {@code mActiveHearingDevice} individual device or
     * the device group accoridng to whether it supports synchronized presets or not.
     * the device group according to whether it supports synchronized presets or not.
     *
     *
     * @param presetIndex an index of one of the available presets
     * @param presetIndex an index of one of the available presets
     */
     */
    public void selectPreset(int presetIndex) {
    public void selectPreset(int presetIndex) {
        if (mActiveHearingDevice == null) {
        if (mActiveHearingDevice == null || mHapClientProfile == null) {
            return;
            return;
        }
        }
        mSelectedPresetIndex = presetIndex;
        mSelectedPresetIndex = presetIndex;
@@ -217,7 +228,7 @@ public class HearingDevicesPresetsController implements
     * @return a list of all known preset info
     * @return a list of all known preset info
     */
     */
    public List<BluetoothHapPresetInfo> getAllPresetInfo() {
    public List<BluetoothHapPresetInfo> getAllPresetInfo() {
        if (mActiveHearingDevice == null) {
        if (mActiveHearingDevice == null || mHapClientProfile == null) {
            return emptyList();
            return emptyList();
        }
        }
        return mHapClientProfile.getAllPresetInfo(mActiveHearingDevice.getDevice()).stream().filter(
        return mHapClientProfile.getAllPresetInfo(mActiveHearingDevice.getDevice()).stream().filter(
@@ -230,14 +241,14 @@ public class HearingDevicesPresetsController implements
     * @return active preset index
     * @return active preset index
     */
     */
    public int getActivePresetIndex() {
    public int getActivePresetIndex() {
        if (mActiveHearingDevice == null) {
        if (mActiveHearingDevice == null || mHapClientProfile == null) {
            return BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
            return BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
        }
        }
        return mHapClientProfile.getActivePresetIndex(mActiveHearingDevice.getDevice());
        return mHapClientProfile.getActivePresetIndex(mActiveHearingDevice.getDevice());
    }
    }


    private void selectPresetSynchronously(int groupId, int presetIndex) {
    private void selectPresetSynchronously(int groupId, int presetIndex) {
        if (mActiveHearingDevice == null) {
        if (mActiveHearingDevice == null || mHapClientProfile == null) {
            return;
            return;
        }
        }
        if (DEBUG) {
        if (DEBUG) {
@@ -250,7 +261,7 @@ public class HearingDevicesPresetsController implements
    }
    }


    private void selectPresetIndependently(int presetIndex) {
    private void selectPresetIndependently(int presetIndex) {
        if (mActiveHearingDevice == null) {
        if (mActiveHearingDevice == null || mHapClientProfile == null) {
            return;
            return;
        }
        }
        if (DEBUG) {
        if (DEBUG) {
+285 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2024 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.systemui.accessibility.hearingaid;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import static java.util.Collections.emptyList;

import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHapClient;
import android.bluetooth.BluetoothHapPresetInfo;
import android.testing.TestableLooper;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HapClientProfile;
import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.SysuiTestCase;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import java.util.List;
import java.util.concurrent.Executor;

/** Tests for {@link HearingDevicesPresetsController}. */
@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
public class HearingDevicesPresetsControllerTest extends SysuiTestCase {

    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;
    private static final int TEST_REASON = 1024;

    @Rule
    public MockitoRule mockito = MockitoJUnit.rule();

    @Mock
    private LocalBluetoothProfileManager mProfileManager;
    @Mock
    private HapClientProfile mHapClientProfile;
    @Mock
    private CachedBluetoothDevice mCachedBluetoothDevice;
    @Mock
    private CachedBluetoothDevice mSubCachedBluetoothDevice;
    @Mock
    private BluetoothDevice mBluetoothDevice;
    @Mock
    private BluetoothDevice mSubBluetoothDevice;

    @Mock
    private HearingDevicesPresetsController.PresetCallback mCallback;

    private HearingDevicesPresetsController mController;

    @Before
    public void setUp() {
        when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
        when(mHapClientProfile.isProfileReady()).thenReturn(true);
        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
        when(mCachedBluetoothDevice.getSubDevice()).thenReturn(mSubCachedBluetoothDevice);
        when(mSubCachedBluetoothDevice.getDevice()).thenReturn(mSubBluetoothDevice);

        mController = new HearingDevicesPresetsController(mProfileManager, mCallback);
    }

    @Test
    public void onServiceConnected_callExpectedCallback() {
        mController.onServiceConnected();

        verify(mHapClientProfile).registerCallback(any(Executor.class),
                any(BluetoothHapClient.Callback.class));
        verify(mCallback).onPresetInfoUpdated(anyList(), anyInt());
    }

    @Test
    public void getAllPresetInfo_setInvalidHearingDevice_getEmpty() {
        when(mCachedBluetoothDevice.getProfiles()).thenReturn(emptyList());
        mController.setHearingDeviceIfSupportHap(mCachedBluetoothDevice);
        BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
        when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
                List.of(hapPresetInfo));

        assertThat(mController.getAllPresetInfo()).isEmpty();
    }

    @Test
    public void getAllPresetInfo_containsNotAvailablePresetInfo_getEmpty() {
        setValidHearingDeviceSupportHap();
        BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(false);
        when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
                List.of(hapPresetInfo));

        assertThat(mController.getAllPresetInfo()).isEmpty();
    }

    @Test
    public void getAllPresetInfo_containsOnePresetInfo_getOnePresetInfo() {
        setValidHearingDeviceSupportHap();
        BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
        when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
                List.of(hapPresetInfo));

        assertThat(mController.getAllPresetInfo()).contains(hapPresetInfo);
    }

    @Test
    public void getActivePresetIndex_getExpectedIndex() {
        setValidHearingDeviceSupportHap();
        when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn(
                TEST_PRESET_INDEX);

        assertThat(mController.getActivePresetIndex()).isEqualTo(TEST_PRESET_INDEX);
    }

    @Test
    public void onPresetSelected_presetIndex_callOnPresetInfoUpdatedWithExpectedPresetIndex() {
        setValidHearingDeviceSupportHap();
        BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
        when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
                List.of(hapPresetInfo));
        when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn(
                TEST_PRESET_INDEX);

        mController.onPresetSelected(mBluetoothDevice, TEST_PRESET_INDEX, TEST_REASON);

        verify(mCallback).onPresetInfoUpdated(eq(List.of(hapPresetInfo)), eq(TEST_PRESET_INDEX));
    }

    @Test
    public void onPresetInfoChanged_presetIndex_callOnPresetInfoUpdatedWithExpectedPresetIndex() {
        setValidHearingDeviceSupportHap();
        BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
        when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
                List.of(hapPresetInfo));
        when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn(
                TEST_PRESET_INDEX);

        mController.onPresetInfoChanged(mBluetoothDevice, List.of(hapPresetInfo), TEST_REASON);

        verify(mCallback).onPresetInfoUpdated(List.of(hapPresetInfo), TEST_PRESET_INDEX);
    }

    @Test
    public void onPresetSelectionFailed_callOnPresetCommandFailed() {
        setValidHearingDeviceSupportHap();

        mController.onPresetSelectionFailed(mBluetoothDevice, TEST_REASON);

        verify(mCallback).onPresetCommandFailed(TEST_REASON);
    }

    @Test
    public void onSetPresetNameFailed_callOnPresetCommandFailed() {
        setValidHearingDeviceSupportHap();

        mController.onSetPresetNameFailed(mBluetoothDevice, TEST_REASON);

        verify(mCallback).onPresetCommandFailed(TEST_REASON);
    }

    @Test
    public void onPresetSelectionForGroupFailed_callSelectPresetIndividual() {
        setValidHearingDeviceSupportHap();
        mController.selectPreset(TEST_PRESET_INDEX);
        Mockito.reset(mHapClientProfile);
        when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID);

        mController.onPresetSelectionForGroupFailed(TEST_HAP_GROUP_ID, TEST_REASON);


        verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX);
        verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX);
    }

    @Test
    public void onSetPresetNameForGroupFailed_callOnPresetCommandFailed() {
        setValidHearingDeviceSupportHap();

        mController.onSetPresetNameForGroupFailed(TEST_HAP_GROUP_ID, TEST_REASON);

        verify(mCallback).onPresetCommandFailed(TEST_REASON);
    }

    @Test
    public void registerHapCallback_callHapRegisterCallback() {
        mController.registerHapCallback();

        verify(mHapClientProfile).registerCallback(any(Executor.class),
                any(BluetoothHapClient.Callback.class));
    }

    @Test
    public void unregisterHapCallback_callHapUnregisterCallback() {
        mController.unregisterHapCallback();

        verify(mHapClientProfile).unregisterCallback(any(BluetoothHapClient.Callback.class));
    }

    @Test
    public void selectPreset_supportSynchronized_validGroupId_callSelectPresetForGroup() {
        setValidHearingDeviceSupportHap();
        when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(true);
        when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID);

        mController.selectPreset(TEST_PRESET_INDEX);

        verify(mHapClientProfile).selectPresetForGroup(TEST_HAP_GROUP_ID, TEST_PRESET_INDEX);
    }

    @Test
    public void selectPreset_supportSynchronized_invalidGroupId_callSelectPresetIndividual() {
        setValidHearingDeviceSupportHap();
        when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(true);
        when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(
                BluetoothCsipSetCoordinator.GROUP_ID_INVALID);

        mController.selectPreset(TEST_PRESET_INDEX);

        verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX);
        verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX);
    }

    @Test
    public void selectPreset_notSupportSynchronized_validGroupId_callSelectPresetIndividual() {
        setValidHearingDeviceSupportHap();
        when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(false);
        when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID);

        mController.selectPreset(TEST_PRESET_INDEX);

        verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX);
        verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX);
    }

    private BluetoothHapPresetInfo getHapPresetInfo(boolean available) {
        BluetoothHapPresetInfo info = mock(BluetoothHapPresetInfo.class);
        when(info.getName()).thenReturn(TEST_PRESET_NAME);
        when(info.getIndex()).thenReturn(TEST_PRESET_INDEX);
        when(info.isAvailable()).thenReturn(available);
        return info;
    }

    private void setValidHearingDeviceSupportHap() {
        LocalBluetoothProfile hapClientProfile = mock(HapClientProfile.class);
        List<LocalBluetoothProfile> profiles = List.of(hapClientProfile);
        when(mCachedBluetoothDevice.getProfiles()).thenReturn(profiles);

        mController.setHearingDeviceIfSupportHap(mCachedBluetoothDevice);
    }
}