1 /* 2 * Copyright (C) 2017 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.BluetoothProfile; 26 import android.bluetooth.BluetoothUuid; 27 import android.content.Intent; 28 import android.os.HandlerThread; 29 import android.os.ParcelUuid; 30 31 import androidx.test.filters.MediumTest; 32 import androidx.test.runner.AndroidJUnit4; 33 34 import com.android.bluetooth.TestUtils; 35 import com.android.bluetooth.a2dp.A2dpService; 36 import com.android.bluetooth.hfp.HeadsetService; 37 38 import org.junit.After; 39 import org.junit.Before; 40 import org.junit.Test; 41 import org.junit.runner.RunWith; 42 import org.mockito.Mock; 43 import org.mockito.MockitoAnnotations; 44 45 import java.util.ArrayList; 46 import java.util.Collections; 47 48 @MediumTest 49 @RunWith(AndroidJUnit4.class) 50 public class PhonePolicyTest { 51 private static final int MAX_CONNECTED_AUDIO_DEVICES = 5; 52 private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250; 53 private static final int CONNECT_OTHER_PROFILES_TIMEOUT_MILLIS = 1000; 54 private static final int CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS = 55 CONNECT_OTHER_PROFILES_TIMEOUT_MILLIS * 3 / 2; 56 57 private HandlerThread mHandlerThread; 58 private BluetoothAdapter mAdapter; 59 private PhonePolicy mPhonePolicy; 60 61 @Mock private AdapterService mAdapterService; 62 @Mock private ServiceFactory mServiceFactory; 63 @Mock private HeadsetService mHeadsetService; 64 @Mock private A2dpService mA2dpService; 65 66 @Before setUp()67 public void setUp() throws Exception { 68 MockitoAnnotations.initMocks(this); 69 // Stub A2DP and HFP 70 when(mHeadsetService.connect(any(BluetoothDevice.class))).thenReturn(true); 71 when(mA2dpService.connect(any(BluetoothDevice.class))).thenReturn(true); 72 // Prepare the TestUtils 73 TestUtils.setAdapterService(mAdapterService); 74 // Configure the maximum connected audio devices 75 doReturn(MAX_CONNECTED_AUDIO_DEVICES).when(mAdapterService).getMaxConnectedAudioDevices(); 76 // Setup the mocked factory to return mocked services 77 doReturn(mHeadsetService).when(mServiceFactory).getHeadsetService(); 78 doReturn(mA2dpService).when(mServiceFactory).getA2dpService(); 79 // Start handler thread for this test 80 mHandlerThread = new HandlerThread("PhonePolicyTestHandlerThread"); 81 mHandlerThread.start(); 82 // Mock the looper 83 doReturn(mHandlerThread.getLooper()).when(mAdapterService).getMainLooper(); 84 // Tell the AdapterService that it is a mock (see isMock documentation) 85 doReturn(true).when(mAdapterService).isMock(); 86 // Must be called to initialize services 87 mAdapter = BluetoothAdapter.getDefaultAdapter(); 88 PhonePolicy.sConnectOtherProfilesTimeoutMillis = CONNECT_OTHER_PROFILES_TIMEOUT_MILLIS; 89 mPhonePolicy = new PhonePolicy(mAdapterService, mServiceFactory); 90 } 91 92 @After tearDown()93 public void tearDown() throws Exception { 94 mHandlerThread.quit(); 95 TestUtils.clearAdapterService(mAdapterService); 96 } 97 98 /** 99 * Test that when new UUIDs are refreshed for a device then we set the priorities for various 100 * profiles accurately. The following profiles should have ON priorities: 101 * A2DP, HFP, HID and PAN 102 */ 103 @Test testProcessInitProfilePriorities()104 public void testProcessInitProfilePriorities() { 105 BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0); 106 // Mock the HeadsetService to return undefined priority 107 when(mHeadsetService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED); 108 109 // Mock the A2DP service to return undefined priority 110 when(mA2dpService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED); 111 112 // Inject an event for UUIDs updated for a remote device with only HFP enabled 113 Intent intent = new Intent(BluetoothDevice.ACTION_UUID); 114 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 115 ParcelUuid[] uuids = new ParcelUuid[2]; 116 uuids[0] = BluetoothUuid.Handsfree; 117 uuids[1] = BluetoothUuid.AudioSink; 118 intent.putExtra(BluetoothDevice.EXTRA_UUID, uuids); 119 mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent); 120 121 // Check that the priorities of the devices for preferred profiles are set to ON 122 verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(eq(device), 123 eq(BluetoothProfile.PRIORITY_ON)); 124 verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(eq(device), 125 eq(BluetoothProfile.PRIORITY_ON)); 126 } 127 128 /** 129 * Test that when the adapter is turned ON then we call autoconnect on devices that have HFP and 130 * A2DP enabled. NOTE that the assumption is that we have already done the pairing previously 131 * and hence the priorities for the device is already set to AUTO_CONNECT over HFP and A2DP (as 132 * part of post pairing process). 133 */ 134 @Test testAdapterOnAutoConnect()135 public void testAdapterOnAutoConnect() { 136 // Return desired values from the mocked object(s) 137 when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); 138 when(mAdapterService.isQuietModeEnabled()).thenReturn(false); 139 140 // Return a list of bonded devices (just one) 141 BluetoothDevice[] bondedDevices = new BluetoothDevice[1]; 142 bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0); 143 when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices); 144 145 // Return PRIORITY_AUTO_CONNECT over HFP and A2DP 146 when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn( 147 BluetoothProfile.PRIORITY_AUTO_CONNECT); 148 when(mA2dpService.getPriority(bondedDevices[0])).thenReturn( 149 BluetoothProfile.PRIORITY_AUTO_CONNECT); 150 151 // Inject an event that the adapter is turned on. 152 Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED); 153 intent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_ON); 154 mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent); 155 156 // Check that we got a request to connect over HFP and A2DP 157 verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connect(eq(bondedDevices[0])); 158 verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connect(eq(bondedDevices[0])); 159 } 160 161 /** 162 * Test that when an auto connect device is disconnected, its priority is set to ON if its 163 * original priority is auto connect 164 */ 165 @Test testDisconnectNoAutoConnect()166 public void testDisconnectNoAutoConnect() { 167 // Return desired values from the mocked object(s) 168 when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); 169 when(mAdapterService.isQuietModeEnabled()).thenReturn(false); 170 171 // Return a list of bonded devices (just one) 172 BluetoothDevice[] bondedDevices = new BluetoothDevice[4]; 173 bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0); 174 bondedDevices[1] = TestUtils.getTestDevice(mAdapter, 1); 175 bondedDevices[2] = TestUtils.getTestDevice(mAdapter, 2); 176 bondedDevices[3] = TestUtils.getTestDevice(mAdapter, 3); 177 when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices); 178 179 // Make all devices auto connect 180 when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn( 181 BluetoothProfile.PRIORITY_AUTO_CONNECT); 182 when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn( 183 BluetoothProfile.PRIORITY_AUTO_CONNECT); 184 when(mHeadsetService.getPriority(bondedDevices[2])).thenReturn( 185 BluetoothProfile.PRIORITY_AUTO_CONNECT); 186 when(mHeadsetService.getPriority(bondedDevices[3])).thenReturn( 187 BluetoothProfile.PRIORITY_OFF); 188 189 // Make one of the device active 190 Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); 191 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]); 192 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 193 mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent); 194 195 // All other disconnected device's priority is set to ON, except disabled ones 196 verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[0], 197 BluetoothProfile.PRIORITY_ON); 198 verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[1], 199 BluetoothProfile.PRIORITY_ON); 200 verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[2], 201 BluetoothProfile.PRIORITY_ON); 202 verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[0], 203 BluetoothProfile.PRIORITY_AUTO_CONNECT); 204 verify(mHeadsetService, never()).setPriority(eq(bondedDevices[3]), anyInt()); 205 when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn( 206 BluetoothProfile.PRIORITY_ON); 207 when(mHeadsetService.getPriority(bondedDevices[2])).thenReturn( 208 BluetoothProfile.PRIORITY_ON); 209 210 // Make another device active 211 when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn( 212 BluetoothProfile.STATE_CONNECTED); 213 intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); 214 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[1]); 215 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 216 mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent); 217 218 // This device should be set to auto connect while the first device is reset to ON 219 verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).setPriority( 220 bondedDevices[0], BluetoothProfile.PRIORITY_ON); 221 verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[1], 222 BluetoothProfile.PRIORITY_AUTO_CONNECT); 223 verify(mHeadsetService, never()).setPriority(eq(bondedDevices[3]), anyInt()); 224 when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn( 225 BluetoothProfile.PRIORITY_ON); 226 when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn( 227 BluetoothProfile.PRIORITY_AUTO_CONNECT); 228 229 // Set active device to null 230 when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn( 231 BluetoothProfile.STATE_DISCONNECTED); 232 intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); 233 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, (BluetoothDevice) null); 234 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 235 mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent); 236 237 // Verify that the priority of previous active device won't be changed while active device 238 // set to null 239 verify(mHeadsetService, after(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setPriority( 240 bondedDevices[1], BluetoothProfile.PRIORITY_ON); 241 verify(mHeadsetService).setPriority(bondedDevices[1], 242 BluetoothProfile.PRIORITY_AUTO_CONNECT); 243 verify(mHeadsetService, never()).setPriority(bondedDevices[1], 244 BluetoothProfile.PRIORITY_OFF); 245 246 // Make the current active device fail to connect 247 when(mA2dpService.getConnectionState(bondedDevices[1])).thenReturn( 248 BluetoothProfile.STATE_DISCONNECTED); 249 updateProfileConnectionStateHelper(bondedDevices[1], BluetoothProfile.HEADSET, 250 BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); 251 252 // This device should be set to ON 253 verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).setPriority( 254 bondedDevices[1], BluetoothProfile.PRIORITY_ON); 255 256 // Verify that we are not setting priorities to random devices and values 257 verify(mHeadsetService, times(7)).setPriority(any(BluetoothDevice.class), anyInt()); 258 } 259 260 /** 261 * Test that we will try to re-connect to a profile on a device if other profile(s) are 262 * connected. This is to add robustness to the connection mechanism 263 */ 264 @Test testReconnectOnPartialConnect()265 public void testReconnectOnPartialConnect() { 266 // Return a list of bonded devices (just one) 267 BluetoothDevice[] bondedDevices = new BluetoothDevice[1]; 268 bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0); 269 when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices); 270 271 // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are 272 // auto-connectable. 273 when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn( 274 BluetoothProfile.PRIORITY_AUTO_CONNECT); 275 when(mA2dpService.getPriority(bondedDevices[0])).thenReturn( 276 BluetoothProfile.PRIORITY_AUTO_CONNECT); 277 278 when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); 279 280 // We want to trigger (in CONNECT_OTHER_PROFILES_TIMEOUT) a call to connect A2DP 281 // To enable that we need to make sure that HeadsetService returns the device as list of 282 // connected devices 283 ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>(); 284 hsConnectedDevices.add(bondedDevices[0]); 285 when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices); 286 // Also the A2DP should say that its not connected for same device 287 when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn( 288 BluetoothProfile.STATE_DISCONNECTED); 289 290 // We send a connection successful for one profile since the re-connect *only* works if we 291 // have already connected successfully over one of the profiles 292 updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET, 293 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); 294 295 // Check that we get a call to A2DP connect 296 verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect( 297 eq(bondedDevices[0])); 298 } 299 300 /** 301 * Test that we will try to re-connect to a profile on a device next time if a previous attempt 302 * failed partially. This will make sure the connection mechanism still works at next try while 303 * the previous attempt is some profiles connected on a device but some not. 304 */ 305 @Test testReconnectOnPartialConnect_PreviousPartialFail()306 public void testReconnectOnPartialConnect_PreviousPartialFail() { 307 // Return a list of bonded devices (just one) 308 BluetoothDevice[] bondedDevices = new BluetoothDevice[1]; 309 bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0); 310 when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices); 311 312 // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are 313 // auto-connectable. 314 when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn( 315 BluetoothProfile.PRIORITY_AUTO_CONNECT); 316 when(mA2dpService.getPriority(bondedDevices[0])).thenReturn( 317 BluetoothProfile.PRIORITY_AUTO_CONNECT); 318 319 when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); 320 321 // We want to trigger (in CONNECT_OTHER_PROFILES_TIMEOUT) a call to connect A2DP 322 // To enable that we need to make sure that HeadsetService returns the device among a list 323 // of connected devices 324 ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>(); 325 hsConnectedDevices.add(bondedDevices[0]); 326 when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices); 327 // Also the A2DP should say that its not connected for same device 328 when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn( 329 BluetoothProfile.STATE_DISCONNECTED); 330 331 // We send a connection success event for one profile since the re-connect *only* works if 332 // we have already connected successfully over one of the profiles 333 updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET, 334 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); 335 336 // Check that we get a call to A2DP reconnect 337 verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect( 338 bondedDevices[0]); 339 340 // We send a connection failure event for the attempted profile, and keep the connected 341 // profile connected. 342 updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.A2DP, 343 BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); 344 345 TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); 346 347 // Verify no one changes the priority of the failed profile 348 verify(mA2dpService, never()).setPriority(eq(bondedDevices[0]), anyInt()); 349 350 // Send a connection success event for one profile again without disconnecting all profiles 351 updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET, 352 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); 353 354 // Check that we won't get a call to A2DP reconnect again before all profiles disconnected 355 verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect( 356 bondedDevices[0]); 357 358 // Send a disconnection event for all connected profiles 359 hsConnectedDevices.remove(bondedDevices[0]); 360 updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET, 361 BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED); 362 363 TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); 364 365 // Send a connection success event for one profile again to trigger re-connect 366 hsConnectedDevices.add(bondedDevices[0]); 367 updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET, 368 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); 369 370 // Check that we get a call to A2DP connect again 371 verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).times(2)).connect( 372 bondedDevices[0]); 373 } 374 375 /** 376 * Test that a second device will auto-connect if there is already one connected device. 377 * 378 * Even though we currently only set one device to be auto connect. The consumer of the auto 379 * connect property works independently so that we will connect to all devices that are in 380 * auto connect mode. 381 */ 382 @Test testAutoConnectMultipleDevices()383 public void testAutoConnectMultipleDevices() { 384 final int kMaxTestDevices = 3; 385 BluetoothDevice[] testDevices = new BluetoothDevice[kMaxTestDevices]; 386 ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>(); 387 ArrayList<BluetoothDevice> a2dpConnectedDevices = new ArrayList<>(); 388 BluetoothDevice a2dpNotConnectedDevice1 = null; 389 BluetoothDevice a2dpNotConnectedDevice2 = null; 390 391 for (int i = 0; i < kMaxTestDevices; i++) { 392 BluetoothDevice testDevice = TestUtils.getTestDevice(mAdapter, i); 393 testDevices[i] = testDevice; 394 395 // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles 396 // are auto-connectable. 397 when(mHeadsetService.getPriority(testDevice)).thenReturn( 398 BluetoothProfile.PRIORITY_AUTO_CONNECT); 399 when(mA2dpService.getPriority(testDevice)).thenReturn( 400 BluetoothProfile.PRIORITY_AUTO_CONNECT); 401 // We want to trigger (in CONNECT_OTHER_PROFILES_TIMEOUT) a call to connect A2DP 402 // To enable that we need to make sure that HeadsetService returns the device as list 403 // of connected devices. 404 hsConnectedDevices.add(testDevice); 405 // Connect A2DP for all devices except the last one 406 if (i < (kMaxTestDevices - 2)) { 407 a2dpConnectedDevices.add(testDevice); 408 } 409 } 410 a2dpNotConnectedDevice1 = hsConnectedDevices.get(kMaxTestDevices - 1); 411 a2dpNotConnectedDevice2 = hsConnectedDevices.get(kMaxTestDevices - 2); 412 413 when(mAdapterService.getBondedDevices()).thenReturn(testDevices); 414 when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); 415 when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices); 416 when(mA2dpService.getConnectedDevices()).thenReturn(a2dpConnectedDevices); 417 // Two of the A2DP devices are not connected 418 when(mA2dpService.getConnectionState(a2dpNotConnectedDevice1)).thenReturn( 419 BluetoothProfile.STATE_DISCONNECTED); 420 when(mA2dpService.getConnectionState(a2dpNotConnectedDevice2)).thenReturn( 421 BluetoothProfile.STATE_DISCONNECTED); 422 423 // We send a connection successful for one profile since the re-connect *only* works if we 424 // have already connected successfully over one of the profiles 425 updateProfileConnectionStateHelper(a2dpNotConnectedDevice1, BluetoothProfile.HEADSET, 426 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); 427 428 // We send a connection successful for one profile since the re-connect *only* works if we 429 // have already connected successfully over one of the profiles 430 updateProfileConnectionStateHelper(a2dpNotConnectedDevice2, BluetoothProfile.HEADSET, 431 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); 432 433 // Check that we get a call to A2DP connect 434 verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect( 435 eq(a2dpNotConnectedDevice1)); 436 verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect( 437 eq(a2dpNotConnectedDevice2)); 438 } 439 440 /** 441 * Test that the connect priority of all devices are set as appropriate if there is one 442 * connected device. 443 * - The HFP and A2DP connect priority for connected devices is set to 444 * BluetoothProfile.PRIORITY_AUTO_CONNECT 445 * - The HFP and A2DP connect priority for bonded devices is set to 446 * BluetoothProfile.PRIORITY_ON 447 */ 448 @Test testSetPriorityMultipleDevices()449 public void testSetPriorityMultipleDevices() { 450 // testDevices[0] - connected for both HFP and A2DP 451 // testDevices[1] - connected only for HFP - will auto-connect for A2DP 452 // testDevices[2] - connected only for A2DP - will auto-connect for HFP 453 // testDevices[3] - not connected 454 final int kMaxTestDevices = 4; 455 BluetoothDevice[] testDevices = new BluetoothDevice[kMaxTestDevices]; 456 ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>(); 457 ArrayList<BluetoothDevice> a2dpConnectedDevices = new ArrayList<>(); 458 459 for (int i = 0; i < kMaxTestDevices; i++) { 460 BluetoothDevice testDevice = TestUtils.getTestDevice(mAdapter, i); 461 testDevices[i] = testDevice; 462 463 // Connect HFP and A2DP for each device as appropriate. 464 // Return PRIORITY_AUTO_CONNECT only for testDevices[0] 465 if (i == 0) { 466 hsConnectedDevices.add(testDevice); 467 a2dpConnectedDevices.add(testDevice); 468 when(mHeadsetService.getPriority(testDevice)).thenReturn( 469 BluetoothProfile.PRIORITY_AUTO_CONNECT); 470 when(mA2dpService.getPriority(testDevice)).thenReturn( 471 BluetoothProfile.PRIORITY_AUTO_CONNECT); 472 } 473 if (i == 1) { 474 hsConnectedDevices.add(testDevice); 475 when(mHeadsetService.getPriority(testDevice)).thenReturn( 476 BluetoothProfile.PRIORITY_ON); 477 when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON); 478 } 479 if (i == 2) { 480 a2dpConnectedDevices.add(testDevice); 481 when(mHeadsetService.getPriority(testDevice)).thenReturn( 482 BluetoothProfile.PRIORITY_ON); 483 when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON); 484 } 485 if (i == 3) { 486 // Device not connected 487 when(mHeadsetService.getPriority(testDevice)).thenReturn( 488 BluetoothProfile.PRIORITY_ON); 489 when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON); 490 } 491 } 492 when(mAdapterService.getBondedDevices()).thenReturn(testDevices); 493 when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); 494 when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices); 495 when(mA2dpService.getConnectedDevices()).thenReturn(a2dpConnectedDevices); 496 // Some of the devices are not connected 497 // testDevices[0] - connected for both HFP and A2DP 498 when(mHeadsetService.getConnectionState(testDevices[0])).thenReturn( 499 BluetoothProfile.STATE_CONNECTED); 500 when(mA2dpService.getConnectionState(testDevices[0])).thenReturn( 501 BluetoothProfile.STATE_CONNECTED); 502 // testDevices[1] - connected only for HFP - will auto-connect for A2DP 503 when(mHeadsetService.getConnectionState(testDevices[1])).thenReturn( 504 BluetoothProfile.STATE_CONNECTED); 505 when(mA2dpService.getConnectionState(testDevices[1])).thenReturn( 506 BluetoothProfile.STATE_DISCONNECTED); 507 // testDevices[2] - connected only for A2DP - will auto-connect for HFP 508 when(mHeadsetService.getConnectionState(testDevices[2])).thenReturn( 509 BluetoothProfile.STATE_DISCONNECTED); 510 when(mA2dpService.getConnectionState(testDevices[2])).thenReturn( 511 BluetoothProfile.STATE_CONNECTED); 512 // testDevices[3] - not connected 513 when(mHeadsetService.getConnectionState(testDevices[3])).thenReturn( 514 BluetoothProfile.STATE_DISCONNECTED); 515 when(mA2dpService.getConnectionState(testDevices[3])).thenReturn( 516 BluetoothProfile.STATE_DISCONNECTED); 517 518 519 // Generate connection state changed for HFP for testDevices[1] and trigger 520 // auto-connect for A2DP. 521 updateProfileConnectionStateHelper(testDevices[1], BluetoothProfile.HEADSET, 522 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); 523 524 // Check that we get a call to A2DP connect 525 verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect( 526 eq(testDevices[1])); 527 528 // testDevices[1] auto-connect completed for A2DP 529 a2dpConnectedDevices.add(testDevices[1]); 530 when(mA2dpService.getConnectedDevices()).thenReturn(a2dpConnectedDevices); 531 when(mA2dpService.getConnectionState(testDevices[1])).thenReturn( 532 BluetoothProfile.STATE_CONNECTED); 533 534 // Check the connect priorities for all devices 535 // - testDevices[0] - connected for HFP and A2DP: setPriority() should not be called 536 // - testDevices[1] - connection state changed for HFP should no longer trigger auto 537 // connect priority change since it is now triggered by A2DP active 538 // device change intent 539 // - testDevices[2] - connected for A2DP: setPriority() should not be called 540 // - testDevices[3] - not connected for HFP nor A2DP: setPriority() should not be called 541 verify(mHeadsetService, times(0)).setPriority(eq(testDevices[0]), anyInt()); 542 verify(mA2dpService, times(0)).setPriority(eq(testDevices[0]), anyInt()); 543 verify(mHeadsetService, times(0)).setPriority(eq(testDevices[1]), 544 eq(BluetoothProfile.PRIORITY_AUTO_CONNECT)); 545 verify(mA2dpService, times(0)).setPriority(eq(testDevices[1]), anyInt()); 546 verify(mHeadsetService, times(0)).setPriority(eq(testDevices[2]), anyInt()); 547 verify(mA2dpService, times(0)).setPriority(eq(testDevices[2]), anyInt()); 548 verify(mHeadsetService, times(0)).setPriority(eq(testDevices[3]), anyInt()); 549 verify(mA2dpService, times(0)).setPriority(eq(testDevices[3]), anyInt()); 550 clearInvocations(mHeadsetService, mA2dpService); 551 552 // Generate connection state changed for A2DP for testDevices[2] and trigger 553 // auto-connect for HFP. 554 updateProfileConnectionStateHelper(testDevices[2], BluetoothProfile.A2DP, 555 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); 556 557 // Check that we get a call to HFP connect 558 verify(mHeadsetService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect( 559 eq(testDevices[2])); 560 561 // testDevices[2] auto-connect completed for HFP 562 hsConnectedDevices.add(testDevices[2]); 563 when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices); 564 when(mHeadsetService.getConnectionState(testDevices[2])).thenReturn( 565 BluetoothProfile.STATE_CONNECTED); 566 567 // Check the connect priorities for all devices 568 // - testDevices[0] - connected for HFP and A2DP: setPriority() should not be called 569 // - testDevices[1] - connected for HFP and A2DP: setPriority() should not be called 570 // - testDevices[2] - connection state changed for A2DP should no longer trigger auto 571 // connect priority change since it is now triggered by A2DP 572 // active device change intent 573 // - testDevices[3] - not connected for HFP nor A2DP: setPriority() should not be called 574 verify(mHeadsetService, times(0)).setPriority(eq(testDevices[0]), anyInt()); 575 verify(mA2dpService, times(0)).setPriority(eq(testDevices[0]), anyInt()); 576 verify(mHeadsetService, times(0)).setPriority(eq(testDevices[1]), anyInt()); 577 verify(mA2dpService, times(0)).setPriority(eq(testDevices[1]), anyInt()); 578 verify(mHeadsetService, times(0)).setPriority(eq(testDevices[2]), anyInt()); 579 verify(mA2dpService, times(0)).setPriority(eq(testDevices[2]), 580 eq(BluetoothProfile.PRIORITY_AUTO_CONNECT)); 581 verify(mHeadsetService, times(0)).setPriority(eq(testDevices[3]), anyInt()); 582 verify(mA2dpService, times(0)).setPriority(eq(testDevices[3]), anyInt()); 583 clearInvocations(mHeadsetService, mA2dpService); 584 } 585 586 /** 587 * Test that we will not try to reconnect on a profile if all the connections failed 588 */ 589 @Test testNoReconnectOnNoConnect()590 public void testNoReconnectOnNoConnect() { 591 // Return a list of bonded devices (just one) 592 BluetoothDevice[] bondedDevices = new BluetoothDevice[1]; 593 bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0); 594 when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices); 595 596 // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are 597 // auto-connectable. 598 when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn( 599 BluetoothProfile.PRIORITY_AUTO_CONNECT); 600 when(mA2dpService.getPriority(bondedDevices[0])).thenReturn( 601 BluetoothProfile.PRIORITY_AUTO_CONNECT); 602 603 when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); 604 605 // Return an empty list simulating that the above connection successful was nullified 606 when(mHeadsetService.getConnectedDevices()).thenReturn(Collections.emptyList()); 607 when(mA2dpService.getConnectedDevices()).thenReturn(Collections.emptyList()); 608 609 // Both A2DP and HFP should say this device is not connected, except for the intent 610 when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn( 611 BluetoothProfile.STATE_DISCONNECTED); 612 when(mHeadsetService.getConnectionState(bondedDevices[0])).thenReturn( 613 BluetoothProfile.STATE_DISCONNECTED); 614 615 // We send a connection successful for one profile since the re-connect *only* works if we 616 // have already connected successfully over one of the profiles 617 Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 618 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]); 619 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED); 620 intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED); 621 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 622 mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent); 623 624 // Check that we don't get any calls to reconnect 625 verify(mA2dpService, after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never()).connect( 626 eq(bondedDevices[0])); 627 verify(mHeadsetService, never()).connect(eq(bondedDevices[0])); 628 } 629 630 /** 631 * Test that we will not try to reconnect on a profile if all the connections failed 632 * with multiple devices 633 */ 634 @Test testNoReconnectOnNoConnect_MultiDevice()635 public void testNoReconnectOnNoConnect_MultiDevice() { 636 // Return a list of bonded devices (just one) 637 BluetoothDevice[] bondedDevices = new BluetoothDevice[2]; 638 bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0); 639 bondedDevices[1] = TestUtils.getTestDevice(mAdapter, 1); 640 when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices); 641 642 // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are 643 // auto-connectable. 644 when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn( 645 BluetoothProfile.PRIORITY_AUTO_CONNECT); 646 when(mA2dpService.getPriority(bondedDevices[0])).thenReturn( 647 BluetoothProfile.PRIORITY_AUTO_CONNECT); 648 when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn( 649 BluetoothProfile.PRIORITY_AUTO_CONNECT); 650 when(mA2dpService.getPriority(bondedDevices[1])).thenReturn( 651 BluetoothProfile.PRIORITY_AUTO_CONNECT); 652 653 when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); 654 655 // Return an a list with only the second device as connected 656 when(mHeadsetService.getConnectedDevices()).thenReturn( 657 Collections.singletonList(bondedDevices[1])); 658 when(mA2dpService.getConnectedDevices()).thenReturn( 659 Collections.singletonList(bondedDevices[1])); 660 661 // Both A2DP and HFP should say this device is not connected, except for the intent 662 when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn( 663 BluetoothProfile.STATE_DISCONNECTED); 664 when(mHeadsetService.getConnectionState(bondedDevices[0])).thenReturn( 665 BluetoothProfile.STATE_DISCONNECTED); 666 when(mA2dpService.getConnectionState(bondedDevices[1])).thenReturn( 667 BluetoothProfile.STATE_CONNECTED); 668 when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn( 669 BluetoothProfile.STATE_CONNECTED); 670 671 // We send a connection successful for one profile since the re-connect *only* works if we 672 // have already connected successfully over one of the profiles 673 Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 674 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]); 675 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED); 676 intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED); 677 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 678 mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent); 679 680 // Check that we don't get any calls to reconnect 681 verify(mA2dpService, after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never()).connect( 682 eq(bondedDevices[0])); 683 verify(mHeadsetService, never()).connect(eq(bondedDevices[0])); 684 } 685 686 /** 687 * Test that we will try to connect to other profiles of a device if it is partially connected 688 */ 689 @Test testReconnectOnPartialConnect_MultiDevice()690 public void testReconnectOnPartialConnect_MultiDevice() { 691 // Return a list of bonded devices (just one) 692 BluetoothDevice[] bondedDevices = new BluetoothDevice[2]; 693 bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0); 694 bondedDevices[1] = TestUtils.getTestDevice(mAdapter, 1); 695 when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices); 696 697 // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are 698 // auto-connectable. 699 when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn( 700 BluetoothProfile.PRIORITY_AUTO_CONNECT); 701 when(mA2dpService.getPriority(bondedDevices[0])).thenReturn( 702 BluetoothProfile.PRIORITY_AUTO_CONNECT); 703 when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn( 704 BluetoothProfile.PRIORITY_AUTO_CONNECT); 705 when(mA2dpService.getPriority(bondedDevices[1])).thenReturn( 706 BluetoothProfile.PRIORITY_AUTO_CONNECT); 707 708 when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON); 709 710 // Return an a list with only the second device as connected 711 when(mHeadsetService.getConnectedDevices()).thenReturn( 712 Collections.singletonList(bondedDevices[1])); 713 when(mA2dpService.getConnectedDevices()).thenReturn(Collections.emptyList()); 714 715 // Both A2DP and HFP should say this device is not connected, except for the intent 716 when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn( 717 BluetoothProfile.STATE_DISCONNECTED); 718 when(mHeadsetService.getConnectionState(bondedDevices[0])).thenReturn( 719 BluetoothProfile.STATE_DISCONNECTED); 720 when(mA2dpService.getConnectionState(bondedDevices[1])).thenReturn( 721 BluetoothProfile.STATE_DISCONNECTED); 722 723 // We send a connection successful for one profile since the re-connect *only* works if we 724 // have already connected successfully over one of the profiles 725 updateProfileConnectionStateHelper(bondedDevices[1], BluetoothProfile.HEADSET, 726 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); 727 728 // Check that we don't get any calls to reconnect 729 verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect( 730 eq(bondedDevices[1])); 731 } 732 733 /** 734 * Test that a device with no supported uuids is initialized properly and does not crash the 735 * stack 736 */ 737 @Test testNoSupportedUuids()738 public void testNoSupportedUuids() { 739 // Mock the HeadsetService to return undefined priority 740 BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0); 741 when(mHeadsetService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED); 742 743 // Mock the A2DP service to return undefined priority 744 when(mA2dpService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED); 745 746 // Inject an event for UUIDs updated for a remote device with only HFP enabled 747 Intent intent = new Intent(BluetoothDevice.ACTION_UUID); 748 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 749 750 // Put no UUIDs 751 mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent); 752 753 // Check that we do not crash and not call any setPriority methods 754 verify(mHeadsetService, 755 after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never()).setPriority(eq(device), 756 eq(BluetoothProfile.PRIORITY_ON)); 757 verify(mA2dpService, never()).setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON)); 758 } 759 updateProfileConnectionStateHelper(BluetoothDevice device, int profileId, int nextState, int prevState)760 private void updateProfileConnectionStateHelper(BluetoothDevice device, int profileId, 761 int nextState, int prevState) { 762 Intent intent; 763 switch (profileId) { 764 case BluetoothProfile.A2DP: 765 when(mA2dpService.getConnectionState(device)).thenReturn(nextState); 766 intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 767 break; 768 case BluetoothProfile.HEADSET: 769 when(mHeadsetService.getConnectionState(device)).thenReturn(nextState); 770 intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 771 break; 772 default: 773 intent = new Intent(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); 774 break; 775 } 776 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 777 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); 778 intent.putExtra(BluetoothProfile.EXTRA_STATE, nextState); 779 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 780 mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent); 781 } 782 } 783