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_A2DP; 20 import static android.media.AudioSystem.DEVICE_OUT_EARPIECE; 21 import static android.media.AudioSystem.DEVICE_OUT_HEARING_AID; 22 import static android.media.AudioSystem.DEVICE_OUT_REMOTE_SUBMIX; 23 24 import static com.google.common.truth.Truth.assertThat; 25 26 import static org.mockito.Mockito.mock; 27 import static org.mockito.Mockito.never; 28 import static org.mockito.Mockito.spy; 29 import static org.mockito.Mockito.verify; 30 import static org.mockito.Mockito.when; 31 32 import android.bluetooth.BluetoothAdapter; 33 import android.bluetooth.BluetoothDevice; 34 import android.bluetooth.BluetoothManager; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.media.AudioManager; 38 39 import androidx.preference.Preference; 40 import androidx.preference.PreferenceManager; 41 import androidx.preference.PreferenceScreen; 42 43 import com.android.settings.R; 44 import com.android.settings.bluetooth.Utils; 45 import com.android.settings.testutils.shadow.ShadowAudioManager; 46 import com.android.settings.testutils.shadow.ShadowBluetoothUtils; 47 import com.android.settingslib.bluetooth.A2dpProfile; 48 import com.android.settingslib.bluetooth.BluetoothEventManager; 49 import com.android.settingslib.bluetooth.HearingAidProfile; 50 import com.android.settingslib.bluetooth.LocalBluetoothManager; 51 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; 52 import com.android.settingslib.media.MediaOutputSliceConstants; 53 54 import org.junit.After; 55 import org.junit.Before; 56 import org.junit.Test; 57 import org.junit.runner.RunWith; 58 import org.mockito.ArgumentCaptor; 59 import org.mockito.Mock; 60 import org.mockito.MockitoAnnotations; 61 import org.robolectric.RobolectricTestRunner; 62 import org.robolectric.RuntimeEnvironment; 63 import org.robolectric.annotation.Config; 64 import org.robolectric.shadows.ShadowBluetoothDevice; 65 66 import java.util.ArrayList; 67 import java.util.List; 68 69 @RunWith(RobolectricTestRunner.class) 70 @Config(shadows = { 71 ShadowAudioManager.class, 72 ShadowBluetoothUtils.class, 73 ShadowBluetoothDevice.class} 74 ) 75 public class MediaOutputPreferenceControllerTest { 76 private static final String TEST_KEY = "Test_Key"; 77 private static final String TEST_DEVICE_NAME_1 = "Test_A2DP_BT_Device_NAME_1"; 78 private static final String TEST_DEVICE_NAME_2 = "Test_A2DP_BT_Device_NAME_2"; 79 private static final String TEST_HAP_DEVICE_NAME_1 = "Test_HAP_BT_Device_NAME_1"; 80 private static final String TEST_HAP_DEVICE_NAME_2 = "Test_HAP_BT_Device_NAME_2"; 81 private static final String TEST_DEVICE_ADDRESS_1 = "00:A1:A1:A1:A1:A1"; 82 private static final String TEST_DEVICE_ADDRESS_2 = "00:B2:B2:B2:B2:B2"; 83 private static final String TEST_DEVICE_ADDRESS_3 = "00:C3:C3:C3:C3:C3"; 84 private static final String TEST_DEVICE_ADDRESS_4 = "00:D4:D4:D4:D4:D4"; 85 86 @Mock 87 private LocalBluetoothManager mLocalManager; 88 @Mock 89 private BluetoothEventManager mBluetoothEventManager; 90 @Mock 91 private LocalBluetoothProfileManager mLocalBluetoothProfileManager; 92 @Mock 93 private A2dpProfile mA2dpProfile; 94 @Mock 95 private HearingAidProfile mHearingAidProfile; 96 @Mock 97 private AudioSwitchPreferenceController.AudioSwitchCallback mAudioSwitchPreferenceCallback; 98 99 private Context mContext; 100 private PreferenceScreen mScreen; 101 private Preference mPreference; 102 private AudioManager mAudioManager; 103 private ShadowAudioManager mShadowAudioManager; 104 private BluetoothManager mBluetoothManager; 105 private BluetoothAdapter mBluetoothAdapter; 106 private BluetoothDevice mBluetoothDevice; 107 private BluetoothDevice mSecondBluetoothDevice; 108 private BluetoothDevice mLeftBluetoothHapDevice; 109 private BluetoothDevice mRightBluetoothHapDevice; 110 private LocalBluetoothManager mLocalBluetoothManager; 111 private MediaOutputPreferenceController mController; 112 private List<BluetoothDevice> mProfileConnectedDevices; 113 private List<BluetoothDevice> mHearingAidActiveDevices; 114 115 @Before setUp()116 public void setUp() { 117 MockitoAnnotations.initMocks(this); 118 mContext = spy(RuntimeEnvironment.application); 119 120 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 121 mShadowAudioManager = ShadowAudioManager.getShadow(); 122 123 ShadowBluetoothUtils.sLocalBluetoothManager = mLocalManager; 124 mLocalBluetoothManager = Utils.getLocalBtManager(mContext); 125 126 when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager); 127 when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager); 128 when(mLocalBluetoothProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); 129 when(mLocalBluetoothProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile); 130 131 mBluetoothManager = new BluetoothManager(mContext); 132 mBluetoothAdapter = mBluetoothManager.getAdapter(); 133 134 mBluetoothDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_1)); 135 when(mBluetoothDevice.getName()).thenReturn(TEST_DEVICE_NAME_1); 136 when(mBluetoothDevice.isConnected()).thenReturn(true); 137 138 mSecondBluetoothDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_2)); 139 when(mSecondBluetoothDevice.getName()).thenReturn(TEST_DEVICE_NAME_2); 140 when(mSecondBluetoothDevice.isConnected()).thenReturn(true); 141 142 mLeftBluetoothHapDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_3)); 143 when(mLeftBluetoothHapDevice.getName()).thenReturn(TEST_HAP_DEVICE_NAME_1); 144 when(mLeftBluetoothHapDevice.isConnected()).thenReturn(true); 145 146 mRightBluetoothHapDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_4)); 147 when(mRightBluetoothHapDevice.getName()).thenReturn(TEST_HAP_DEVICE_NAME_2); 148 when(mRightBluetoothHapDevice.isConnected()).thenReturn(true); 149 150 mController = new MediaOutputPreferenceController(mContext, TEST_KEY); 151 mScreen = spy(new PreferenceScreen(mContext, null)); 152 mPreference = new Preference(mContext); 153 mProfileConnectedDevices = new ArrayList<>(); 154 mHearingAidActiveDevices = new ArrayList<>(2); 155 156 when(mScreen.getPreferenceManager()).thenReturn(mock(PreferenceManager.class)); 157 when(mScreen.getContext()).thenReturn(mContext); 158 when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); 159 mScreen.addPreference(mPreference); 160 mController.displayPreference(mScreen); 161 mController.setCallback(mAudioSwitchPreferenceCallback); 162 } 163 164 @After tearDown()165 public void tearDown() { 166 ShadowBluetoothUtils.reset(); 167 } 168 169 170 /** 171 * A2DP Bluetooth device(s) are not connected nor previously connected 172 * Preference should be invisible 173 */ 174 @Test updateState_withoutConnectedBtDevice_preferenceInvisible()175 public void updateState_withoutConnectedBtDevice_preferenceInvisible() { 176 mShadowAudioManager.setOutputDevice(DEVICE_OUT_EARPIECE); 177 mAudioManager.setMode(AudioManager.MODE_NORMAL); 178 mProfileConnectedDevices.clear(); 179 when(mA2dpProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices); 180 mPreference.setVisible(true); 181 182 assertThat(mPreference.isVisible()).isTrue(); 183 mController.updateState(mPreference); 184 assertThat(mPreference.isVisible()).isFalse(); 185 } 186 187 /** 188 * A2DP Bluetooth device(s) are connected, no matter active or inactive 189 * Preference should be visible 190 */ 191 @Test updateState_withConnectedBtDevice_preferenceVisible()192 public void updateState_withConnectedBtDevice_preferenceVisible() { 193 mShadowAudioManager.setOutputDevice(DEVICE_OUT_BLUETOOTH_A2DP); 194 mAudioManager.setMode(AudioManager.MODE_NORMAL); 195 mProfileConnectedDevices.clear(); 196 mProfileConnectedDevices.add(mBluetoothDevice); 197 when(mA2dpProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices); 198 assertThat(mPreference.isVisible()).isFalse(); 199 200 // Without Active Bluetooth Device 201 mController.updateState(mPreference); 202 assertThat(mPreference.isVisible()).isTrue(); 203 204 // With Active Bluetooth Device 205 when(mA2dpProfile.getActiveDevice()).thenReturn(mBluetoothDevice); 206 mController.updateState(mPreference); 207 assertThat(mPreference.isVisible()).isTrue(); 208 } 209 210 /** 211 * A2DP Bluetooth device(s) are connected, but no device is set as activated 212 * Preference summary should be "This device" 213 */ 214 @Test updateState_withConnectedBtDevice_withoutActiveBtDevice_setDefaultSummary()215 public void updateState_withConnectedBtDevice_withoutActiveBtDevice_setDefaultSummary() { 216 mShadowAudioManager.setOutputDevice(DEVICE_OUT_EARPIECE); 217 mAudioManager.setMode(AudioManager.MODE_NORMAL); 218 mProfileConnectedDevices.clear(); 219 mProfileConnectedDevices.add(mBluetoothDevice); 220 mProfileConnectedDevices.add(mSecondBluetoothDevice); 221 when(mA2dpProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices); 222 when(mA2dpProfile.getActiveDevice()).thenReturn(null); 223 224 assertThat(mPreference.getSummary()).isNull(); 225 mController.updateState(mPreference); 226 assertThat(mPreference.getSummary()).isEqualTo( 227 mContext.getText(R.string.media_output_default_summary)); 228 } 229 230 /** 231 * A2DP Bluetooth device(s) are connected and active 232 * Preference summary should be device's name 233 */ 234 @Test updateState_withActiveBtDevice_setActivatedDeviceName()235 public void updateState_withActiveBtDevice_setActivatedDeviceName() { 236 mShadowAudioManager.setOutputDevice(DEVICE_OUT_BLUETOOTH_A2DP); 237 mAudioManager.setMode(AudioManager.MODE_NORMAL); 238 mProfileConnectedDevices.clear(); 239 mProfileConnectedDevices.add(mBluetoothDevice); 240 mProfileConnectedDevices.add(mSecondBluetoothDevice); 241 when(mA2dpProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices); 242 when(mA2dpProfile.getActiveDevice()).thenReturn(mBluetoothDevice); 243 244 assertThat(mPreference.getSummary()).isNull(); 245 mController.updateState(mPreference); 246 assertThat(mPreference.getSummary()).isEqualTo(TEST_DEVICE_NAME_1); 247 } 248 249 250 /** 251 * Hearing Aid device(s) are connected, no matter active or inactive 252 * Preference should be visible 253 */ 254 @Test updateState_withConnectedHADevice_preferenceVisible()255 public void updateState_withConnectedHADevice_preferenceVisible() { 256 mShadowAudioManager.setOutputDevice(DEVICE_OUT_HEARING_AID); 257 mAudioManager.setMode(AudioManager.MODE_NORMAL); 258 mHearingAidActiveDevices.clear(); 259 mHearingAidActiveDevices.add(mLeftBluetoothHapDevice); 260 when(mHearingAidProfile.getConnectedDevices()).thenReturn(mHearingAidActiveDevices); 261 assertThat(mPreference.isVisible()).isFalse(); 262 263 // Without Active Hearing Aid Device 264 mController.updateState(mPreference); 265 assertThat(mPreference.isVisible()).isTrue(); 266 267 // With Active Hearing Aid Device 268 when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices); 269 mController.updateState(mPreference); 270 assertThat(mPreference.isVisible()).isTrue(); 271 } 272 273 /** 274 * Hearing Aid device(s) are connected and active 275 * Preference summary should be device's name 276 */ 277 @Test updateState_withActiveHADevice_setActivatedDeviceName()278 public void updateState_withActiveHADevice_setActivatedDeviceName() { 279 mShadowAudioManager.setOutputDevice(DEVICE_OUT_HEARING_AID); 280 mAudioManager.setMode(AudioManager.MODE_NORMAL); 281 mHearingAidActiveDevices.clear(); 282 mHearingAidActiveDevices.add(mLeftBluetoothHapDevice); 283 when(mHearingAidProfile.getConnectedDevices()).thenReturn(mHearingAidActiveDevices); 284 when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices); 285 286 assertThat(mPreference.getSummary()).isNull(); 287 mController.updateState(mPreference); 288 assertThat(mPreference.getSummary()).isEqualTo(TEST_HAP_DEVICE_NAME_1); 289 290 } 291 292 @Test click_launch_outputSwitcherSlice()293 public void click_launch_outputSwitcherSlice() { 294 final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); 295 mController.handlePreferenceTreeClick(mPreference); 296 verify(mContext, never()).startActivity(intentCaptor.capture()); 297 298 mPreference.setKey(TEST_KEY); 299 mController.handlePreferenceTreeClick(mPreference); 300 verify(mContext).startActivity(intentCaptor.capture()); 301 assertThat(intentCaptor.getValue().getAction()) 302 .isEqualTo(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT); 303 } 304 305 /** 306 * Default status 307 * Preference should be invisible 308 * Summary should be default summary 309 */ 310 @Test updateState_shouldSetSummary()311 public void updateState_shouldSetSummary() { 312 mController.updateState(mPreference); 313 314 assertThat(mPreference.isVisible()).isFalse(); 315 assertThat(mPreference.getSummary()).isEqualTo( 316 mContext.getText(R.string.media_output_default_summary)); 317 } 318 319 /** 320 * During a call 321 * Preference should be invisible 322 * Default string should be "Unavailable during calls" 323 */ 324 @Test updateState_duringACall_shouldSetDefaultSummary()325 public void updateState_duringACall_shouldSetDefaultSummary() { 326 mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); 327 328 mController.updateState(mPreference); 329 330 assertThat(mPreference.isVisible()).isFalse(); 331 assertThat(mPreference.getSummary()).isEqualTo( 332 mContext.getText(R.string.media_out_summary_ongoing_call_state)); 333 } 334 335 @Test findActiveDevice_onlyA2dpDeviceActive_returnA2dpDevice()336 public void findActiveDevice_onlyA2dpDeviceActive_returnA2dpDevice() { 337 when(mLocalBluetoothProfileManager.getHearingAidProfile()).thenReturn(null); 338 when(mA2dpProfile.getActiveDevice()).thenReturn(mBluetoothDevice); 339 340 assertThat(mController.findActiveDevice()).isEqualTo(mBluetoothDevice); 341 } 342 343 @Test findActiveDevice_allDevicesNotActive_returnNull()344 public void findActiveDevice_allDevicesNotActive_returnNull() { 345 when(mLocalBluetoothProfileManager.getHearingAidProfile()).thenReturn(null); 346 when(mA2dpProfile.getActiveDevice()).thenReturn(null); 347 348 assertThat(mController.findActiveDevice()).isNull(); 349 } 350 } 351