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