• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.sound;
18 
19 import static android.media.AudioSystem.DEVICE_OUT_ALL_SCO;
20 import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
21 import static android.media.AudioSystem.STREAM_MUSIC;
22 
23 import static com.android.settings.core.BasePreferenceController.AVAILABLE;
24 import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
25 
26 import static com.google.common.truth.Truth.assertThat;
27 
28 import static org.mockito.ArgumentMatchers.any;
29 import static org.mockito.Mockito.mock;
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.BluetoothAdapter;
35 import android.bluetooth.BluetoothDevice;
36 import android.bluetooth.BluetoothManager;
37 import android.content.BroadcastReceiver;
38 import android.content.Context;
39 import android.content.IntentFilter;
40 import android.content.pm.PackageManager;
41 import android.media.AudioManager;
42 import android.util.FeatureFlagUtils;
43 
44 import androidx.preference.ListPreference;
45 import androidx.preference.PreferenceManager;
46 import androidx.preference.PreferenceScreen;
47 
48 import com.android.settings.R;
49 import com.android.settings.bluetooth.Utils;
50 import com.android.settings.core.FeatureFlags;
51 import com.android.settings.testutils.shadow.ShadowAudioManager;
52 import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
53 import com.android.settingslib.bluetooth.A2dpProfile;
54 import com.android.settingslib.bluetooth.BluetoothCallback;
55 import com.android.settingslib.bluetooth.BluetoothEventManager;
56 import com.android.settingslib.bluetooth.HeadsetProfile;
57 import com.android.settingslib.bluetooth.HearingAidProfile;
58 import com.android.settingslib.bluetooth.LocalBluetoothManager;
59 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
60 
61 import org.junit.After;
62 import org.junit.Before;
63 import org.junit.Test;
64 import org.junit.runner.RunWith;
65 import org.mockito.Mock;
66 import org.mockito.MockitoAnnotations;
67 import org.robolectric.RobolectricTestRunner;
68 import org.robolectric.RuntimeEnvironment;
69 import org.robolectric.Shadows;
70 import org.robolectric.annotation.Config;
71 import org.robolectric.shadow.api.Shadow;
72 import org.robolectric.shadows.ShadowBluetoothDevice;
73 import org.robolectric.shadows.ShadowPackageManager;
74 
75 import java.util.ArrayList;
76 import java.util.List;
77 
78 @RunWith(RobolectricTestRunner.class)
79 @Config(shadows = {
80         ShadowAudioManager.class,
81         ShadowBluetoothUtils.class,
82         ShadowBluetoothDevice.class}
83 )
84 public class AudioOutputSwitchPreferenceControllerTest {
85     private static final String TEST_KEY = "Test_Key";
86     private static final String TEST_DEVICE_NAME_1 = "Test_A2DP_BT_Device_NAME_1";
87     private static final String TEST_DEVICE_NAME_2 = "Test_A2DP_BT_Device_NAME_2";
88     private static final String TEST_DEVICE_ADDRESS_1 = "00:A1:A1:A1:A1:A1";
89     private static final String TEST_DEVICE_ADDRESS_2 = "00:B2:B2:B2:B2:B2";
90     private static final String TEST_DEVICE_ADDRESS_3 = "00:C3:C3:C3:C3:C3";
91     private final static long HISYNCID1 = 10;
92     private final static long HISYNCID2 = 11;
93 
94     @Mock
95     private LocalBluetoothManager mLocalManager;
96     @Mock
97     private BluetoothEventManager mBluetoothEventManager;
98     @Mock
99     private LocalBluetoothProfileManager mLocalBluetoothProfileManager;
100     @Mock
101     private A2dpProfile mA2dpProfile;
102     @Mock
103     private HeadsetProfile mHeadsetProfile;
104     @Mock
105     private HearingAidProfile mHearingAidProfile;
106 
107     private Context mContext;
108     private PreferenceScreen mScreen;
109     private ListPreference mPreference;
110     private AudioManager mAudioManager;
111     private ShadowAudioManager mShadowAudioManager;
112     private BluetoothManager mBluetoothManager;
113     private BluetoothAdapter mBluetoothAdapter;
114     private BluetoothDevice mBluetoothDevice;
115     private BluetoothDevice mLeftBluetoothHapDevice;
116     private BluetoothDevice mRightBluetoothHapDevice;
117     private LocalBluetoothManager mLocalBluetoothManager;
118     private AudioSwitchPreferenceController mController;
119     private List<BluetoothDevice> mProfileConnectedDevices;
120     private List<BluetoothDevice> mHearingAidActiveDevices;
121     private List<BluetoothDevice> mEmptyDevices;
122     private ShadowPackageManager mPackageManager;
123 
124     @Before
setUp()125     public void setUp() {
126         MockitoAnnotations.initMocks(this);
127         mContext = spy(RuntimeEnvironment.application);
128 
129         mAudioManager = mContext.getSystemService(AudioManager.class);
130         mShadowAudioManager = ShadowAudioManager.getShadow();
131 
132         ShadowBluetoothUtils.sLocalBluetoothManager = mLocalManager;
133         mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
134 
135         when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
136         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
137         when(mLocalBluetoothProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
138         when(mLocalBluetoothProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
139         when(mLocalBluetoothProfileManager.getHeadsetProfile()).thenReturn(mHeadsetProfile);
140         mPackageManager = Shadow.extract(mContext.getPackageManager());
141         mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, true);
142 
143         mBluetoothManager = new BluetoothManager(mContext);
144         mBluetoothAdapter = mBluetoothManager.getAdapter();
145 
146         mBluetoothDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_1));
147         when(mBluetoothDevice.getName()).thenReturn(TEST_DEVICE_NAME_1);
148         when(mBluetoothDevice.isConnected()).thenReturn(true);
149 
150         mLeftBluetoothHapDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_2));
151         when(mLeftBluetoothHapDevice.isConnected()).thenReturn(true);
152         mRightBluetoothHapDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_3));
153         when(mRightBluetoothHapDevice.isConnected()).thenReturn(true);
154 
155         mController = new AudioSwitchPreferenceControllerTestable(mContext, TEST_KEY);
156         mScreen = spy(new PreferenceScreen(mContext, null));
157         mPreference = new ListPreference(mContext);
158         mProfileConnectedDevices = new ArrayList<>();
159         mHearingAidActiveDevices = new ArrayList<>(2);
160         mEmptyDevices = new ArrayList<>(2);
161 
162         when(mScreen.getPreferenceManager()).thenReturn(mock(PreferenceManager.class));
163         when(mScreen.getContext()).thenReturn(mContext);
164         when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
165         mScreen.addPreference(mPreference);
166         mController.displayPreference(mScreen);
167     }
168 
169     @After
tearDown()170     public void tearDown() {
171         ShadowBluetoothUtils.reset();
172     }
173 
174     @Test
constructor_notSupportBluetooth_shouldReturnBeforeUsingLocalBluetoothManager()175     public void constructor_notSupportBluetooth_shouldReturnBeforeUsingLocalBluetoothManager() {
176         ShadowBluetoothUtils.reset();
177         mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
178 
179         AudioSwitchPreferenceController controller = new AudioSwitchPreferenceControllerTestable(
180                 mContext, TEST_KEY);
181         controller.onStart();
182         controller.onStop();
183 
184         assertThat(mLocalBluetoothManager).isNull();
185     }
186 
187     @Test
getAvailabilityStatus_disableFlagNoBluetoothFeature_returnUnavailable()188     public void getAvailabilityStatus_disableFlagNoBluetoothFeature_returnUnavailable() {
189         FeatureFlagUtils.setEnabled(mContext, FeatureFlags.AUDIO_SWITCHER_SETTINGS, false);
190         mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
191 
192         assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
193     }
194 
195     @Test
getAvailabilityStatus_disableFlagWithBluetoothFeature_returnUnavailable()196     public void getAvailabilityStatus_disableFlagWithBluetoothFeature_returnUnavailable() {
197         FeatureFlagUtils.setEnabled(mContext, FeatureFlags.AUDIO_SWITCHER_SETTINGS, false);
198         mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, true);
199 
200 
201         assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
202     }
203 
204     @Test
getAvailabilityStatus_enableFlagWithBluetoothFeature_returnAvailable()205     public void getAvailabilityStatus_enableFlagWithBluetoothFeature_returnAvailable() {
206         FeatureFlagUtils.setEnabled(mContext, FeatureFlags.AUDIO_SWITCHER_SETTINGS, true);
207         mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, true);
208 
209         assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
210     }
211 
212     @Test
getAvailabilityStatus_enableFlagNoBluetoothFeature_returnUnavailable()213     public void getAvailabilityStatus_enableFlagNoBluetoothFeature_returnUnavailable() {
214         FeatureFlagUtils.setEnabled(mContext, FeatureFlags.AUDIO_SWITCHER_SETTINGS, true);
215         mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
216 
217         assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
218     }
219 
220     @Test
onStart_shouldRegisterCallbackAndRegisterReceiver()221     public void onStart_shouldRegisterCallbackAndRegisterReceiver() {
222         mController.onStart();
223 
224         verify(mLocalBluetoothManager.getEventManager()).registerCallback(
225                 any(BluetoothCallback.class));
226         verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
227         verify(mLocalBluetoothManager).setForegroundActivity(mContext);
228     }
229 
230     @Test
onStop_shouldUnregisterCallbackAndUnregisterReceiver()231     public void onStop_shouldUnregisterCallbackAndUnregisterReceiver() {
232         mController.onStart();
233         mController.onStop();
234 
235         verify(mLocalBluetoothManager.getEventManager()).unregisterCallback(
236                 any(BluetoothCallback.class));
237         verify(mContext).unregisterReceiver(any(BroadcastReceiver.class));
238         verify(mLocalBluetoothManager).setForegroundActivity(null);
239     }
240 
241     /**
242      * Audio stream output to bluetooth sco headset which is the subset of all sco device.
243      * isStreamFromOutputDevice should return true.
244      */
245     @Test
isStreamFromOutputDevice_outputDeviceIsBtScoHeadset_shouldReturnTrue()246     public void isStreamFromOutputDevice_outputDeviceIsBtScoHeadset_shouldReturnTrue() {
247         mShadowAudioManager.setOutputDevice(DEVICE_OUT_BLUETOOTH_SCO_HEADSET);
248 
249         assertThat(mController.isStreamFromOutputDevice(STREAM_MUSIC, DEVICE_OUT_ALL_SCO)).isTrue();
250     }
251 
252     /**
253      * Left side of HAP device is active.
254      * findActiveHearingAidDevice should return hearing aid device active device.
255      */
256     @Test
findActiveHearingAidDevice_leftActiveDevice_returnLeftDeviceAsActiveHapDevice()257     public void findActiveHearingAidDevice_leftActiveDevice_returnLeftDeviceAsActiveHapDevice() {
258         mController.mConnectedDevices.clear();
259         mController.mConnectedDevices.add(mBluetoothDevice);
260         mController.mConnectedDevices.add(mLeftBluetoothHapDevice);
261         mHearingAidActiveDevices.clear();
262         mHearingAidActiveDevices.add(mLeftBluetoothHapDevice);
263         mHearingAidActiveDevices.add(null);
264         when(mHeadsetProfile.getActiveDevice()).thenReturn(mBluetoothDevice);
265         when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices);
266 
267         assertThat(mController.findActiveHearingAidDevice()).isEqualTo(mLeftBluetoothHapDevice);
268     }
269 
270     /**
271      * Right side of HAP device is active.
272      * findActiveHearingAidDevice should return hearing aid device active device.
273      */
274     @Test
findActiveHearingAidDevice_rightActiveDevice_returnRightDeviceAsActiveHapDevice()275     public void findActiveHearingAidDevice_rightActiveDevice_returnRightDeviceAsActiveHapDevice() {
276         mController.mConnectedDevices.clear();
277         mController.mConnectedDevices.add(mBluetoothDevice);
278         mController.mConnectedDevices.add(mRightBluetoothHapDevice);
279         mHearingAidActiveDevices.clear();
280         mHearingAidActiveDevices.add(null);
281         mHearingAidActiveDevices.add(mRightBluetoothHapDevice);
282         when(mHeadsetProfile.getActiveDevice()).thenReturn(mBluetoothDevice);
283         when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices);
284 
285         assertThat(mController.findActiveHearingAidDevice()).isEqualTo(mRightBluetoothHapDevice);
286     }
287 
288     /**
289      * Both are active device.
290      * findActiveHearingAidDevice only return the active device in mConnectedDevices.
291      */
292     @Test
findActiveHearingAidDevice_twoActiveDevice_returnActiveDeviceInConnectedDevices()293     public void findActiveHearingAidDevice_twoActiveDevice_returnActiveDeviceInConnectedDevices() {
294         mController.mConnectedDevices.clear();
295         mController.mConnectedDevices.add(mBluetoothDevice);
296         mController.mConnectedDevices.add(mRightBluetoothHapDevice);
297         mHearingAidActiveDevices.clear();
298         mHearingAidActiveDevices.add(mLeftBluetoothHapDevice);
299         mHearingAidActiveDevices.add(mRightBluetoothHapDevice);
300         when(mHeadsetProfile.getActiveDevice()).thenReturn(mBluetoothDevice);
301         when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices);
302 
303         assertThat(mController.findActiveHearingAidDevice()).isEqualTo(mRightBluetoothHapDevice);
304     }
305 
306     /**
307      * None of them are active.
308      * findActiveHearingAidDevice should return null.
309      */
310     @Test
findActiveHearingAidDevice_noActiveDevice_returnNull()311     public void findActiveHearingAidDevice_noActiveDevice_returnNull() {
312         mController.mConnectedDevices.clear();
313         mController.mConnectedDevices.add(mBluetoothDevice);
314         mController.mConnectedDevices.add(mLeftBluetoothHapDevice);
315         mHearingAidActiveDevices.clear();
316         when(mHeadsetProfile.getActiveDevice()).thenReturn(mBluetoothDevice);
317         when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices);
318 
319         assertThat(mController.findActiveHearingAidDevice()).isNull();
320     }
321 
322     /**
323      * Two hearing aid devices with different HisyncId
324      * getConnectedHearingAidDevices should add both device to list.
325      */
326     @Test
getConnectedHearingAidDevices_deviceHisyncIdIsDifferent_shouldAddBothToList()327     public void getConnectedHearingAidDevices_deviceHisyncIdIsDifferent_shouldAddBothToList() {
328         mEmptyDevices.clear();
329         mProfileConnectedDevices.clear();
330         mProfileConnectedDevices.add(mLeftBluetoothHapDevice);
331         mProfileConnectedDevices.add(mRightBluetoothHapDevice);
332         when(mHearingAidProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices);
333         when(mHearingAidProfile.getHiSyncId(mLeftBluetoothHapDevice)).thenReturn(HISYNCID1);
334         when(mHearingAidProfile.getHiSyncId(mRightBluetoothHapDevice)).thenReturn(HISYNCID2);
335 
336         mEmptyDevices.addAll(mController.getConnectedHearingAidDevices());
337 
338         assertThat(mEmptyDevices).containsExactly(mLeftBluetoothHapDevice,
339                 mRightBluetoothHapDevice);
340     }
341 
342     /**
343      * Two hearing aid devices with same HisyncId
344      * getConnectedHearingAidDevices should only add first device to list.
345      */
346     @Test
getConnectedHearingAidDevices_deviceHisyncIdIsSame_shouldAddOneToList()347     public void getConnectedHearingAidDevices_deviceHisyncIdIsSame_shouldAddOneToList() {
348         mEmptyDevices.clear();
349         mProfileConnectedDevices.clear();
350         mProfileConnectedDevices.add(mLeftBluetoothHapDevice);
351         mProfileConnectedDevices.add(mRightBluetoothHapDevice);
352         when(mHearingAidProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices);
353         when(mHearingAidProfile.getHiSyncId(mLeftBluetoothHapDevice)).thenReturn(HISYNCID1);
354         when(mHearingAidProfile.getHiSyncId(mRightBluetoothHapDevice)).thenReturn(HISYNCID1);
355 
356         mEmptyDevices.addAll(mController.getConnectedHearingAidDevices());
357 
358         assertThat(mEmptyDevices).containsExactly(mLeftBluetoothHapDevice);
359     }
360 
361     /**
362      * One hands free profile device is connected.
363      * getConnectedA2dpDevices should add this device to list.
364      */
365     @Test
getConnectedHfpDevices_oneConnectedHfpDevice_shouldAddDeviceToList()366     public void getConnectedHfpDevices_oneConnectedHfpDevice_shouldAddDeviceToList() {
367         mEmptyDevices.clear();
368         mProfileConnectedDevices.clear();
369         mProfileConnectedDevices.add(mBluetoothDevice);
370         when(mHeadsetProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices);
371 
372         mEmptyDevices.addAll(mController.getConnectedHfpDevices());
373 
374         assertThat(mEmptyDevices).containsExactly(mBluetoothDevice);
375     }
376 
377     /**
378      * More than one hands free profile devices are connected.
379      * getConnectedA2dpDevices should add all devices to list.
380      */
381     @Test
getConnectedHfpDevices_moreThanOneConnectedHfpDevice_shouldAddDeviceToList()382     public void getConnectedHfpDevices_moreThanOneConnectedHfpDevice_shouldAddDeviceToList() {
383         mEmptyDevices.clear();
384         mProfileConnectedDevices.clear();
385         mProfileConnectedDevices.add(mBluetoothDevice);
386         mProfileConnectedDevices.add(mLeftBluetoothHapDevice);
387         when(mHeadsetProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices);
388 
389         mEmptyDevices.addAll(mController.getConnectedHfpDevices());
390 
391         assertThat(mEmptyDevices).containsExactly(mBluetoothDevice, mLeftBluetoothHapDevice);
392     }
393 
394     private class AudioSwitchPreferenceControllerTestable extends
395             AudioSwitchPreferenceController {
AudioSwitchPreferenceControllerTestable(Context context, String key)396         AudioSwitchPreferenceControllerTestable(Context context, String key) {
397             super(context, key);
398         }
399 
400         @Override
findActiveDevice()401         public BluetoothDevice findActiveDevice() {
402             return null;
403         }
404 
405         @Override
getPreferenceKey()406         public String getPreferenceKey() {
407             return TEST_KEY;
408         }
409     }
410 }