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