1 /* 2 * Copyright 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.bluetooth.btservice; 18 19 import static org.mockito.Mockito.*; 20 21 import android.bluetooth.BluetoothA2dp; 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothDevice; 24 import android.bluetooth.BluetoothHeadset; 25 import android.bluetooth.BluetoothHearingAid; 26 import android.bluetooth.BluetoothProfile; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.media.AudioManager; 30 31 import androidx.test.InstrumentationRegistry; 32 import androidx.test.filters.MediumTest; 33 import androidx.test.runner.AndroidJUnit4; 34 35 import com.android.bluetooth.R; 36 import com.android.bluetooth.TestUtils; 37 import com.android.bluetooth.a2dp.A2dpService; 38 import com.android.bluetooth.hearingaid.HearingAidService; 39 import com.android.bluetooth.hfp.HeadsetService; 40 41 import org.junit.After; 42 import org.junit.Assert; 43 import org.junit.Assume; 44 import org.junit.Before; 45 import org.junit.Test; 46 import org.junit.runner.RunWith; 47 import org.mockito.Mock; 48 import org.mockito.MockitoAnnotations; 49 50 @MediumTest 51 @RunWith(AndroidJUnit4.class) 52 public class ActiveDeviceManagerTest { 53 private BluetoothAdapter mAdapter; 54 private Context mContext; 55 private BluetoothDevice mA2dpDevice; 56 private BluetoothDevice mHeadsetDevice; 57 private BluetoothDevice mA2dpHeadsetDevice; 58 private BluetoothDevice mHearingAidDevice; 59 private ActiveDeviceManager mActiveDeviceManager; 60 private static final int TIMEOUT_MS = 1000; 61 62 @Mock private AdapterService mAdapterService; 63 @Mock private ServiceFactory mServiceFactory; 64 @Mock private A2dpService mA2dpService; 65 @Mock private HeadsetService mHeadsetService; 66 @Mock private HearingAidService mHearingAidService; 67 @Mock private AudioManager mAudioManager; 68 69 @Before setUp()70 public void setUp() throws Exception { 71 mContext = InstrumentationRegistry.getTargetContext(); 72 Assume.assumeTrue("Ignore test when A2dpService is not enabled", 73 mContext.getResources().getBoolean(R.bool.profile_supported_a2dp)); 74 Assume.assumeTrue("Ignore test when HeadsetService is not enabled", 75 mContext.getResources().getBoolean(R.bool.profile_supported_hs_hfp)); 76 77 // Set up mocks and test assets 78 MockitoAnnotations.initMocks(this); 79 TestUtils.setAdapterService(mAdapterService); 80 when(mAdapterService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager); 81 when(mServiceFactory.getA2dpService()).thenReturn(mA2dpService); 82 when(mServiceFactory.getHeadsetService()).thenReturn(mHeadsetService); 83 when(mServiceFactory.getHearingAidService()).thenReturn(mHearingAidService); 84 when(mA2dpService.setActiveDevice(any())).thenReturn(true); 85 when(mHeadsetService.setActiveDevice(any())).thenReturn(true); 86 when(mHearingAidService.setActiveDevice(any())).thenReturn(true); 87 88 mActiveDeviceManager = new ActiveDeviceManager(mAdapterService, mServiceFactory); 89 mActiveDeviceManager.start(); 90 mAdapter = BluetoothAdapter.getDefaultAdapter(); 91 92 // Get devices for testing 93 mA2dpDevice = TestUtils.getTestDevice(mAdapter, 0); 94 mHeadsetDevice = TestUtils.getTestDevice(mAdapter, 1); 95 mA2dpHeadsetDevice = TestUtils.getTestDevice(mAdapter, 2); 96 mHearingAidDevice = TestUtils.getTestDevice(mAdapter, 3); 97 } 98 99 @After tearDown()100 public void tearDown() throws Exception { 101 if (!mContext.getResources().getBoolean(R.bool.profile_supported_hs_hfp) 102 || !mContext.getResources().getBoolean(R.bool.profile_supported_a2dp)) { 103 return; 104 } 105 mActiveDeviceManager.cleanup(); 106 TestUtils.clearAdapterService(mAdapterService); 107 } 108 109 @Test testSetUpAndTearDown()110 public void testSetUpAndTearDown() {} 111 112 /** 113 * One A2DP is connected. 114 */ 115 @Test onlyA2dpConnected_setA2dpActive()116 public void onlyA2dpConnected_setA2dpActive() { 117 a2dpConnected(mA2dpDevice); 118 verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice); 119 } 120 121 /** 122 * Two A2DP are connected. Should set the second one active. 123 */ 124 @Test secondA2dpConnected_setSecondA2dpActive()125 public void secondA2dpConnected_setSecondA2dpActive() { 126 a2dpConnected(mA2dpDevice); 127 verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice); 128 129 a2dpConnected(mA2dpHeadsetDevice); 130 verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice); 131 } 132 133 /** 134 * One A2DP is connected and disconnected later. Should then set active device to null. 135 */ 136 @Test lastA2dpDisconnected_clearA2dpActive()137 public void lastA2dpDisconnected_clearA2dpActive() { 138 a2dpConnected(mA2dpDevice); 139 verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice); 140 141 a2dpDisconnected(mA2dpDevice); 142 verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(isNull()); 143 } 144 145 /** 146 * Two A2DP are connected and active device is explicitly set. 147 */ 148 @Test a2dpActiveDeviceSelected_setActive()149 public void a2dpActiveDeviceSelected_setActive() { 150 a2dpConnected(mA2dpDevice); 151 verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice); 152 153 a2dpConnected(mA2dpHeadsetDevice); 154 verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice); 155 156 a2dpActiveDeviceChanged(mA2dpDevice); 157 // Don't call mA2dpService.setActiveDevice() 158 TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper()); 159 verify(mA2dpService, times(1)).setActiveDevice(mA2dpDevice); 160 Assert.assertEquals(mA2dpDevice, mActiveDeviceManager.getA2dpActiveDevice()); 161 } 162 163 /** 164 * One Headset is connected. 165 */ 166 @Test onlyHeadsetConnected_setHeadsetActive()167 public void onlyHeadsetConnected_setHeadsetActive() { 168 headsetConnected(mHeadsetDevice); 169 verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice); 170 } 171 172 /** 173 * Two Headset are connected. Should set the second one active. 174 */ 175 @Test secondHeadsetConnected_setSecondHeadsetActive()176 public void secondHeadsetConnected_setSecondHeadsetActive() { 177 headsetConnected(mHeadsetDevice); 178 verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice); 179 180 headsetConnected(mA2dpHeadsetDevice); 181 verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice); 182 } 183 184 /** 185 * One Headset is connected and disconnected later. Should then set active device to null. 186 */ 187 @Test lastHeadsetDisconnected_clearHeadsetActive()188 public void lastHeadsetDisconnected_clearHeadsetActive() { 189 headsetConnected(mHeadsetDevice); 190 verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice); 191 192 headsetDisconnected(mHeadsetDevice); 193 verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(isNull()); 194 } 195 196 /** 197 * Two Headset are connected and active device is explicitly set. 198 */ 199 @Test headsetActiveDeviceSelected_setActive()200 public void headsetActiveDeviceSelected_setActive() { 201 headsetConnected(mHeadsetDevice); 202 verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice); 203 204 headsetConnected(mA2dpHeadsetDevice); 205 verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice); 206 207 headsetActiveDeviceChanged(mHeadsetDevice); 208 // Don't call mHeadsetService.setActiveDevice() 209 TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper()); 210 verify(mHeadsetService, times(1)).setActiveDevice(mHeadsetDevice); 211 Assert.assertEquals(mHeadsetDevice, mActiveDeviceManager.getHfpActiveDevice()); 212 } 213 214 /** 215 * A combo (A2DP + Headset) device is connected. Then a Hearing Aid is connected. 216 */ 217 @Test hearingAidActive_clearA2dpAndHeadsetActive()218 public void hearingAidActive_clearA2dpAndHeadsetActive() { 219 Assume.assumeTrue("Ignore test when HearingAidService is not enabled", 220 mContext.getResources().getBoolean( 221 com.android.internal.R.bool.config_hearing_aid_profile_supported)); 222 223 a2dpConnected(mA2dpHeadsetDevice); 224 headsetConnected(mA2dpHeadsetDevice); 225 verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice); 226 verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice); 227 228 hearingAidActiveDeviceChanged(mHearingAidDevice); 229 verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(isNull()); 230 verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(isNull()); 231 } 232 233 /** 234 * A Hearing Aid is connected. Then a combo (A2DP + Headset) device is connected. 235 */ 236 @Test hearingAidActive_dontSetA2dpAndHeadsetActive()237 public void hearingAidActive_dontSetA2dpAndHeadsetActive() { 238 Assume.assumeTrue("Ignore test when HearingAidService is not enabled", 239 mContext.getResources().getBoolean( 240 com.android.internal.R.bool.config_hearing_aid_profile_supported)); 241 242 hearingAidActiveDeviceChanged(mHearingAidDevice); 243 a2dpConnected(mA2dpHeadsetDevice); 244 headsetConnected(mA2dpHeadsetDevice); 245 246 TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper()); 247 verify(mA2dpService, never()).setActiveDevice(mA2dpHeadsetDevice); 248 verify(mHeadsetService, never()).setActiveDevice(mA2dpHeadsetDevice); 249 } 250 251 /** 252 * A Hearing Aid is connected. Then an A2DP active device is explicitly set. 253 */ 254 @Test hearingAidActive_setA2dpActiveExplicitly()255 public void hearingAidActive_setA2dpActiveExplicitly() { 256 Assume.assumeTrue("Ignore test when HearingAidService is not enabled", 257 mContext.getResources().getBoolean( 258 com.android.internal.R.bool.config_hearing_aid_profile_supported)); 259 260 hearingAidActiveDeviceChanged(mHearingAidDevice); 261 a2dpConnected(mA2dpHeadsetDevice); 262 a2dpActiveDeviceChanged(mA2dpHeadsetDevice); 263 264 TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper()); 265 verify(mHearingAidService).setActiveDevice(isNull()); 266 // Don't call mA2dpService.setActiveDevice() 267 verify(mA2dpService, never()).setActiveDevice(mA2dpHeadsetDevice); 268 Assert.assertEquals(mA2dpHeadsetDevice, mActiveDeviceManager.getA2dpActiveDevice()); 269 Assert.assertEquals(null, mActiveDeviceManager.getHearingAidActiveDevice()); 270 } 271 272 /** 273 * A Hearing Aid is connected. Then a Headset active device is explicitly set. 274 */ 275 @Test hearingAidActive_setHeadsetActiveExplicitly()276 public void hearingAidActive_setHeadsetActiveExplicitly() { 277 Assume.assumeTrue("Ignore test when HearingAidService is not enabled", 278 mContext.getResources().getBoolean( 279 com.android.internal.R.bool.config_hearing_aid_profile_supported)); 280 281 hearingAidActiveDeviceChanged(mHearingAidDevice); 282 headsetConnected(mA2dpHeadsetDevice); 283 headsetActiveDeviceChanged(mA2dpHeadsetDevice); 284 285 TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper()); 286 verify(mHearingAidService).setActiveDevice(isNull()); 287 // Don't call mHeadsetService.setActiveDevice() 288 verify(mHeadsetService, never()).setActiveDevice(mA2dpHeadsetDevice); 289 Assert.assertEquals(mA2dpHeadsetDevice, mActiveDeviceManager.getHfpActiveDevice()); 290 Assert.assertEquals(null, mActiveDeviceManager.getHearingAidActiveDevice()); 291 } 292 293 /** 294 * A wired audio device is connected. Then all active devices are set to null. 295 */ 296 @Test wiredAudioDeviceConnected_setAllActiveDevicesNull()297 public void wiredAudioDeviceConnected_setAllActiveDevicesNull() { 298 a2dpConnected(mA2dpDevice); 299 headsetConnected(mHeadsetDevice); 300 verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice); 301 verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice); 302 303 mActiveDeviceManager.wiredAudioDeviceConnected(); 304 verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(isNull()); 305 verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(isNull()); 306 verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(isNull()); 307 } 308 309 /** 310 * Helper to indicate A2dp connected for a device. 311 */ a2dpConnected(BluetoothDevice device)312 private void a2dpConnected(BluetoothDevice device) { 313 Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 314 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 315 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED); 316 intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED); 317 mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent); 318 } 319 320 /** 321 * Helper to indicate A2dp disconnected for a device. 322 */ a2dpDisconnected(BluetoothDevice device)323 private void a2dpDisconnected(BluetoothDevice device) { 324 Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 325 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 326 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTED); 327 intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED); 328 mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent); 329 } 330 331 /** 332 * Helper to indicate A2dp active device changed for a device. 333 */ a2dpActiveDeviceChanged(BluetoothDevice device)334 private void a2dpActiveDeviceChanged(BluetoothDevice device) { 335 Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); 336 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 337 mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent); 338 } 339 340 /** 341 * Helper to indicate Headset connected for a device. 342 */ headsetConnected(BluetoothDevice device)343 private void headsetConnected(BluetoothDevice device) { 344 Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 345 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 346 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED); 347 intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED); 348 mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent); 349 } 350 351 /** 352 * Helper to indicate Headset disconnected for a device. 353 */ headsetDisconnected(BluetoothDevice device)354 private void headsetDisconnected(BluetoothDevice device) { 355 Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 356 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 357 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTED); 358 intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED); 359 mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent); 360 } 361 362 /** 363 * Helper to indicate Headset active device changed for a device. 364 */ headsetActiveDeviceChanged(BluetoothDevice device)365 private void headsetActiveDeviceChanged(BluetoothDevice device) { 366 Intent intent = new Intent(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); 367 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 368 mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent); 369 } 370 371 /** 372 * Helper to indicate Hearing Aid active device changed for a device. 373 */ hearingAidActiveDeviceChanged(BluetoothDevice device)374 private void hearingAidActiveDeviceChanged(BluetoothDevice device) { 375 Intent intent = new Intent(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED); 376 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 377 mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent); 378 } 379 } 380