Merge "Fix crash in Hearing Devices dialog when connecting to ASHA device with LE Audio disabled" into main
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index b6fe0df..d08653c3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -228,7 +228,7 @@
mHearingDeviceItemList = getHearingDevicesList();
if (mPresetsController != null) {
activeHearingDevice = getActiveHearingDevice(mHearingDeviceItemList);
- mPresetsController.setActiveHearingDevice(activeHearingDevice);
+ mPresetsController.setHearingDeviceIfSupportHap(activeHearingDevice);
} else {
activeHearingDevice = null;
}
@@ -336,7 +336,7 @@
}
final CachedBluetoothDevice activeHearingDevice = getActiveHearingDevice(
mHearingDeviceItemList);
- mPresetsController.setActiveHearingDevice(activeHearingDevice);
+ mPresetsController.setHearingDeviceIfSupportHap(activeHearingDevice);
mPresetInfoAdapter = new ArrayAdapter<>(dialog.getContext(),
R.layout.hearing_devices_preset_spinner_selected,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java
index f81124e..aa95fd0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java
@@ -113,7 +113,7 @@
@Override
public void onPresetSelectionForGroupFailed(int hapGroupId, int reason) {
- if (mActiveHearingDevice == null) {
+ if (mActiveHearingDevice == null || mHapClientProfile == null) {
return;
}
if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) {
@@ -137,7 +137,7 @@
@Override
public void onSetPresetNameForGroupFailed(int hapGroupId, int reason) {
- if (mActiveHearingDevice == null) {
+ if (mActiveHearingDevice == null || mHapClientProfile == null) {
return;
}
if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) {
@@ -177,22 +177,33 @@
}
/**
- * 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
+ * and support {@link HapClientProfile}.
*/
- public void setActiveHearingDevice(CachedBluetoothDevice activeHearingDevice) {
- mActiveHearingDevice = 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;
+ } else {
+ mActiveHearingDevice = null;
+ }
}
/**
* 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
*/
public void selectPreset(int presetIndex) {
- if (mActiveHearingDevice == null) {
+ if (mActiveHearingDevice == null || mHapClientProfile == null) {
return;
}
mSelectedPresetIndex = presetIndex;
@@ -217,7 +228,7 @@
* @return a list of all known preset info
*/
public List<BluetoothHapPresetInfo> getAllPresetInfo() {
- if (mActiveHearingDevice == null) {
+ if (mActiveHearingDevice == null || mHapClientProfile == null) {
return emptyList();
}
return mHapClientProfile.getAllPresetInfo(mActiveHearingDevice.getDevice()).stream().filter(
@@ -230,14 +241,14 @@
* @return active preset index
*/
public int getActivePresetIndex() {
- if (mActiveHearingDevice == null) {
+ if (mActiveHearingDevice == null || mHapClientProfile == null) {
return BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
}
return mHapClientProfile.getActivePresetIndex(mActiveHearingDevice.getDevice());
}
private void selectPresetSynchronously(int groupId, int presetIndex) {
- if (mActiveHearingDevice == null) {
+ if (mActiveHearingDevice == null || mHapClientProfile == null) {
return;
}
if (DEBUG) {
@@ -250,7 +261,7 @@
}
private void selectPresetIndependently(int presetIndex) {
- if (mActiveHearingDevice == null) {
+ if (mActiveHearingDevice == null || mHapClientProfile == null) {
return;
}
if (DEBUG) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
new file mode 100644
index 0000000..2ac5d10
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
@@ -0,0 +1,285 @@
+/*
+ * 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);
+ }
+}