1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.bluetooth;
18 
19 import static android.bluetooth.BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
20 import static android.bluetooth.BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
21 
22 import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP;
23 import static com.android.settings.bluetooth.BluetoothDetailsHearingAidsPresetsController.KEY_HEARING_AIDS_PRESETS;
24 
25 import static com.google.common.truth.Truth.assertThat;
26 
27 import static org.mockito.ArgumentMatchers.any;
28 import static org.mockito.Mockito.mock;
29 import static org.mockito.Mockito.never;
30 import static org.mockito.Mockito.spy;
31 import static org.mockito.Mockito.verify;
32 import static org.mockito.Mockito.when;
33 
34 import android.bluetooth.BluetoothDevice;
35 import android.bluetooth.BluetoothHapClient;
36 import android.bluetooth.BluetoothHapPresetInfo;
37 
38 import androidx.preference.ListPreference;
39 import androidx.preference.PreferenceCategory;
40 
41 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
42 import com.android.settingslib.bluetooth.HapClientProfile;
43 import com.android.settingslib.bluetooth.LocalBluetoothManager;
44 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
45 
46 import org.junit.Rule;
47 import org.junit.Test;
48 import org.junit.runner.RunWith;
49 import org.mockito.Mock;
50 import org.mockito.junit.MockitoJUnit;
51 import org.mockito.junit.MockitoRule;
52 import org.robolectric.RobolectricTestRunner;
53 
54 import java.util.ArrayList;
55 import java.util.List;
56 import java.util.Set;
57 import java.util.concurrent.Executor;
58 
59 /** Tests for {@link BluetoothDetailsHearingAidsPresetsController}. */
60 @RunWith(RobolectricTestRunner.class)
61 public class BluetoothDetailsHearingAidsPresetsControllerTest extends
62         BluetoothDetailsControllerTestBase {
63 
64     private static final int TEST_PRESET_INDEX = 1;
65     private static final String TEST_PRESET_NAME = "test_preset";
66     private static final int TEST_HAP_GROUP_ID = 1;
67 
68     @Rule
69     public final MockitoRule mockito = MockitoJUnit.rule();
70 
71     @Mock
72     private LocalBluetoothManager mLocalManager;
73     @Mock
74     private LocalBluetoothProfileManager mProfileManager;
75     @Mock
76     private HapClientProfile mHapClientProfile;
77     @Mock
78     private CachedBluetoothDevice mCachedChildDevice;
79     @Mock
80     private BluetoothDevice mChildDevice;
81 
82     private BluetoothDetailsHearingAidsPresetsController mController;
83 
84     @Override
setUp()85     public void setUp() {
86         super.setUp();
87 
88         when(mLocalManager.getProfileManager()).thenReturn(mProfileManager);
89         when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
90         when(mCachedDevice.getDevice()).thenReturn(mDevice);
91         when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile));
92         when(mCachedDevice.isConnectedHapClientDevice()).thenReturn(true);
93         when(mCachedChildDevice.getDevice()).thenReturn(mChildDevice);
94         PreferenceCategory deviceControls = new PreferenceCategory(mContext);
95         deviceControls.setKey(KEY_HEARING_DEVICE_GROUP);
96         mScreen.addPreference(deviceControls);
97         mController = new BluetoothDetailsHearingAidsPresetsController(mContext, mFragment,
98                 mLocalManager, mCachedDevice, mLifecycle);
99         mController.init(mScreen);
100     }
101 
102     @Test
isAvailable_supportHap_returnTrue()103     public void isAvailable_supportHap_returnTrue() {
104         when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile));
105 
106         assertThat(mController.isAvailable()).isTrue();
107     }
108 
109     @Test
isAvailable_notSupportHap_returnFalse()110     public void isAvailable_notSupportHap_returnFalse() {
111         when(mCachedDevice.getProfiles()).thenReturn(new ArrayList<>());
112 
113         assertThat(mController.isAvailable()).isFalse();
114     }
115 
116     @Test
onResume_registerCallback()117     public void onResume_registerCallback() {
118         mController.onResume();
119 
120         verify(mHapClientProfile).registerCallback(any(Executor.class),
121                 any(BluetoothHapClient.Callback.class));
122     }
123 
124     @Test
onPause_unregisterCallback()125     public void onPause_unregisterCallback() {
126         mController.onPause();
127 
128         verify(mHapClientProfile).unregisterCallback(any(BluetoothHapClient.Callback.class));
129     }
130 
131     @Test
onPreferenceChange_keyMatched_verifyStatusUpdated()132     public void onPreferenceChange_keyMatched_verifyStatusUpdated() {
133         final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS);
134 
135         boolean handled = mController.onPreferenceChange(presetPreference,
136                 String.valueOf(TEST_PRESET_INDEX));
137 
138         assertThat(handled).isTrue();
139         verify(presetPreference).setSummary(TEST_PRESET_NAME);
140     }
141 
142     @Test
onPreferenceChange_keyNotMatched_doNothing()143     public void onPreferenceChange_keyNotMatched_doNothing() {
144         final ListPreference presetPreference = getTestPresetPreference("wrong_key");
145 
146         boolean handled = mController.onPreferenceChange(
147                 presetPreference, String.valueOf(TEST_PRESET_INDEX));
148 
149         assertThat(handled).isFalse();
150         verify(presetPreference, never()).setSummary(any());
151     }
152 
153     @Test
onPreferenceChange_supportGroupOperation_validGroupId_verifySelectPresetForGroup()154     public void onPreferenceChange_supportGroupOperation_validGroupId_verifySelectPresetForGroup() {
155         final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS);
156         when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(true);
157         when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(TEST_HAP_GROUP_ID);
158 
159         mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX));
160 
161         verify(mHapClientProfile).selectPresetForGroup(TEST_HAP_GROUP_ID, TEST_PRESET_INDEX);
162     }
163 
164     @Test
onPreferenceChange_notSupportGroupOperation_verifySelectPreset()165     public void onPreferenceChange_notSupportGroupOperation_verifySelectPreset() {
166         final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS);
167         when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(false);
168         when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(TEST_HAP_GROUP_ID);
169 
170         mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX));
171 
172         verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
173     }
174 
175     @Test
onPreferenceChange_invalidGroupId_verifySelectPreset()176     public void onPreferenceChange_invalidGroupId_verifySelectPreset() {
177         final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS);
178         when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(true);
179         when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(GROUP_ID_INVALID);
180 
181         mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX));
182 
183         verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
184     }
185 
186     @Test
onPreferenceChange_notSupportGroupOperation_hasSubDevice_verifyStatusUpdated()187     public void onPreferenceChange_notSupportGroupOperation_hasSubDevice_verifyStatusUpdated() {
188         final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS);
189         when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(false);
190         when(mCachedDevice.getSubDevice()).thenReturn(mCachedChildDevice);
191 
192         mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX));
193 
194         verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
195         verify(mHapClientProfile).selectPreset(mChildDevice, TEST_PRESET_INDEX);
196     }
197 
198     @Test
onPreferenceChange_notSupportGroupOperation_hasMemberDevice_verifyStatusUpdated()199     public void onPreferenceChange_notSupportGroupOperation_hasMemberDevice_verifyStatusUpdated() {
200         final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS);
201         when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(false);
202         when(mCachedDevice.getMemberDevice()).thenReturn(Set.of(mCachedChildDevice));
203 
204         mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX));
205 
206         verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
207         verify(mHapClientProfile).selectPreset(mChildDevice, TEST_PRESET_INDEX);
208     }
209 
210     @Test
refresh_emptyPresetInfo_preferenceDisabled()211     public void refresh_emptyPresetInfo_preferenceDisabled() {
212         when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(new ArrayList<>());
213 
214         mController.refresh();
215 
216         assertThat(mController.getPreference()).isNotNull();
217         assertThat(mController.getPreference().isEnabled()).isFalse();
218     }
219 
220     @Test
refresh_validPresetInfo_preferenceEnabled()221     public void refresh_validPresetInfo_preferenceEnabled() {
222         BluetoothHapPresetInfo info = getTestPresetInfo();
223         when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
224 
225         mController.refresh();
226 
227         assertThat(mController.getPreference()).isNotNull();
228         assertThat(mController.getPreference().isEnabled()).isTrue();
229     }
230 
231     @Test
refresh_invalidActivePresetIndex_summaryIsNull()232     public void refresh_invalidActivePresetIndex_summaryIsNull() {
233         BluetoothHapPresetInfo info = getTestPresetInfo();
234         when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
235         when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(PRESET_INDEX_UNAVAILABLE);
236 
237         mController.refresh();
238 
239         assertThat(mController.getPreference()).isNotNull();
240         assertThat(mController.getPreference().getSummary()).isNull();
241     }
242 
243     @Test
refresh_validActivePresetIndex_summaryIsNotNull()244     public void refresh_validActivePresetIndex_summaryIsNotNull() {
245         BluetoothHapPresetInfo info = getTestPresetInfo();
246         when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
247         when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX);
248 
249         mController.refresh();
250 
251         assertThat(mController.getPreference()).isNotNull();
252         assertThat(mController.getPreference().getSummary()).isNotNull();
253     }
254 
255     @Test
onPresetSelectionForGroupFailed_selectPresetIsCalled()256     public void onPresetSelectionForGroupFailed_selectPresetIsCalled() {
257         when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(TEST_HAP_GROUP_ID);
258         mController.getPreference().setValue(String.valueOf(TEST_PRESET_INDEX));
259 
260         mController.onPresetSelectionForGroupFailed(TEST_HAP_GROUP_ID, TEST_PRESET_INDEX);
261 
262         verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
263     }
264 
getTestPresetInfo()265     private BluetoothHapPresetInfo getTestPresetInfo() {
266         BluetoothHapPresetInfo info = mock(BluetoothHapPresetInfo.class);
267         when(info.getName()).thenReturn(TEST_PRESET_NAME);
268         when(info.getIndex()).thenReturn(TEST_PRESET_INDEX);
269         return info;
270     }
271 
getTestPresetPreference(String key)272     private ListPreference getTestPresetPreference(String key) {
273         final ListPreference presetPreference = spy(new ListPreference(mContext));
274         when(presetPreference.findIndexOfValue(String.valueOf(TEST_PRESET_INDEX))).thenReturn(0);
275         when(presetPreference.getEntries()).thenReturn(new CharSequence[]{TEST_PRESET_NAME});
276         when(presetPreference.getEntryValues()).thenReturn(
277                 new CharSequence[]{String.valueOf(TEST_PRESET_INDEX)});
278         presetPreference.setKey(key);
279         return presetPreference;
280     }
281 }
282