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.a2dp; 18 19 import static org.mockito.Mockito.*; 20 21 import android.bluetooth.BluetoothA2dp; 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothCodecConfig; 24 import android.bluetooth.BluetoothCodecStatus; 25 import android.bluetooth.BluetoothDevice; 26 import android.bluetooth.BluetoothProfile; 27 import android.bluetooth.BluetoothUuid; 28 import android.content.BroadcastReceiver; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.os.Looper; 33 import android.os.ParcelUuid; 34 import android.support.test.InstrumentationRegistry; 35 import android.support.test.filters.MediumTest; 36 import android.support.test.rule.ServiceTestRule; 37 import android.support.test.runner.AndroidJUnit4; 38 39 import com.android.bluetooth.R; 40 import com.android.bluetooth.TestUtils; 41 import com.android.bluetooth.btservice.AdapterService; 42 43 import org.junit.After; 44 import org.junit.Assert; 45 import org.junit.Assume; 46 import org.junit.Before; 47 import org.junit.Rule; 48 import org.junit.Test; 49 import org.junit.runner.RunWith; 50 import org.mockito.Mock; 51 import org.mockito.MockitoAnnotations; 52 53 import java.util.List; 54 import java.util.concurrent.BlockingQueue; 55 import java.util.concurrent.LinkedBlockingQueue; 56 import java.util.concurrent.TimeoutException; 57 58 @MediumTest 59 @RunWith(AndroidJUnit4.class) 60 public class A2dpServiceTest { 61 private static final int MAX_CONNECTED_AUDIO_DEVICES = 5; 62 63 private BluetoothAdapter mAdapter; 64 private Context mTargetContext; 65 private A2dpService mA2dpService; 66 private BluetoothDevice mTestDevice; 67 private static final int TIMEOUT_MS = 1000; // 1s 68 69 private BroadcastReceiver mA2dpIntentReceiver; 70 private final BlockingQueue<Intent> mConnectionStateChangedQueue = new LinkedBlockingQueue<>(); 71 private final BlockingQueue<Intent> mAudioStateChangedQueue = new LinkedBlockingQueue<>(); 72 private final BlockingQueue<Intent> mCodecConfigChangedQueue = new LinkedBlockingQueue<>(); 73 74 @Mock private AdapterService mAdapterService; 75 @Mock private A2dpNativeInterface mA2dpNativeInterface; 76 77 @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); 78 79 @Before 80 public void setUp() throws Exception { 81 mTargetContext = InstrumentationRegistry.getTargetContext(); 82 Assume.assumeTrue("Ignore test when A2dpService is not enabled", 83 mTargetContext.getResources().getBoolean(R.bool.profile_supported_a2dp)); 84 // Set up mocks and test assets 85 MockitoAnnotations.initMocks(this); 86 87 if (Looper.myLooper() == null) { 88 Looper.prepare(); 89 } 90 91 TestUtils.setAdapterService(mAdapterService); 92 doReturn(MAX_CONNECTED_AUDIO_DEVICES).when(mAdapterService).getMaxConnectedAudioDevices(); 93 doReturn(false).when(mAdapterService).isQuietModeEnabled(); 94 95 mAdapter = BluetoothAdapter.getDefaultAdapter(); 96 97 startService(); 98 mA2dpService.mA2dpNativeInterface = mA2dpNativeInterface; 99 100 // Override the timeout value to speed up the test 101 A2dpStateMachine.sConnectTimeoutMs = TIMEOUT_MS; // 1s 102 103 // Set up the Connection State Changed receiver 104 IntentFilter filter = new IntentFilter(); 105 filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 106 filter.addAction(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED); 107 filter.addAction(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED); 108 mA2dpIntentReceiver = new A2dpIntentReceiver(); 109 mTargetContext.registerReceiver(mA2dpIntentReceiver, filter); 110 111 // Get a device for testing 112 mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05"); 113 mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_UNDEFINED); 114 doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService) 115 .getBondState(any(BluetoothDevice.class)); 116 doReturn(new ParcelUuid[]{BluetoothUuid.AudioSink}).when(mAdapterService) 117 .getRemoteUuids(any(BluetoothDevice.class)); 118 } 119 120 @After 121 public void tearDown() throws Exception { 122 if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_a2dp)) { 123 return; 124 } 125 stopService(); 126 mTargetContext.unregisterReceiver(mA2dpIntentReceiver); 127 mConnectionStateChangedQueue.clear(); 128 mAudioStateChangedQueue.clear(); 129 mCodecConfigChangedQueue.clear(); 130 TestUtils.clearAdapterService(mAdapterService); 131 } 132 133 private void startService() throws TimeoutException { 134 TestUtils.startService(mServiceRule, A2dpService.class); 135 mA2dpService = A2dpService.getA2dpService(); 136 Assert.assertNotNull(mA2dpService); 137 } 138 139 private void stopService() throws TimeoutException { 140 TestUtils.stopService(mServiceRule, A2dpService.class); 141 mA2dpService = A2dpService.getA2dpService(); 142 Assert.assertNull(mA2dpService); 143 } 144 145 private class A2dpIntentReceiver extends BroadcastReceiver { 146 @Override 147 public void onReceive(Context context, Intent intent) { 148 if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { 149 try { 150 mConnectionStateChangedQueue.put(intent); 151 } catch (InterruptedException e) { 152 Assert.fail("Cannot add Intent to the Connection State queue: " 153 + e.getMessage()); 154 } 155 } 156 if (BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED.equals(intent.getAction())) { 157 try { 158 mAudioStateChangedQueue.put(intent); 159 } catch (InterruptedException e) { 160 Assert.fail("Cannot add Intent to the Audio State queue: " + e.getMessage()); 161 } 162 } 163 if (BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED.equals(intent.getAction())) { 164 try { 165 mCodecConfigChangedQueue.put(intent); 166 } catch (InterruptedException e) { 167 Assert.fail("Cannot add Intent to the Codec Config queue: " + e.getMessage()); 168 } 169 } 170 } 171 } 172 173 private void verifyConnectionStateIntent(int timeoutMs, BluetoothDevice device, 174 int newState, int prevState) { 175 Intent intent = TestUtils.waitForIntent(timeoutMs, mConnectionStateChangedQueue); 176 Assert.assertNotNull(intent); 177 Assert.assertEquals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, 178 intent.getAction()); 179 Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); 180 Assert.assertEquals(newState, intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); 181 Assert.assertEquals(prevState, intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 182 -1)); 183 } 184 185 private void verifyNoConnectionStateIntent(int timeoutMs) { 186 Intent intent = TestUtils.waitForNoIntent(timeoutMs, mConnectionStateChangedQueue); 187 Assert.assertNull(intent); 188 } 189 190 private void verifyAudioStateIntent(int timeoutMs, BluetoothDevice device, 191 int newState, int prevState) { 192 Intent intent = TestUtils.waitForIntent(timeoutMs, mAudioStateChangedQueue); 193 Assert.assertNotNull(intent); 194 Assert.assertEquals(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED, intent.getAction()); 195 Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); 196 Assert.assertEquals(newState, intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); 197 Assert.assertEquals(prevState, intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 198 -1)); 199 } 200 201 private void verifyNoAudioStateIntent(int timeoutMs) { 202 Intent intent = TestUtils.waitForNoIntent(timeoutMs, mAudioStateChangedQueue); 203 Assert.assertNull(intent); 204 } 205 206 private void verifyCodecConfigIntent(int timeoutMs, BluetoothDevice device, 207 BluetoothCodecStatus codecStatus) { 208 Intent intent = TestUtils.waitForIntent(timeoutMs, mCodecConfigChangedQueue); 209 Assert.assertNotNull(intent); 210 Assert.assertEquals(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED, intent.getAction()); 211 Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); 212 Assert.assertEquals(codecStatus, 213 intent.getParcelableExtra(BluetoothCodecStatus.EXTRA_CODEC_STATUS)); 214 } 215 216 private void verifyNoCodecConfigIntent(int timeoutMs) { 217 Intent intent = TestUtils.waitForNoIntent(timeoutMs, mCodecConfigChangedQueue); 218 Assert.assertNull(intent); 219 } 220 221 /** 222 * Test getting A2DP Service: getA2dpService() 223 */ 224 @Test 225 public void testGetA2dpService() { 226 Assert.assertEquals(mA2dpService, A2dpService.getA2dpService()); 227 } 228 229 /** 230 * Test stop A2DP Service 231 */ 232 @Test 233 public void testStopA2dpService() { 234 // Prepare: connect and set active device 235 doReturn(true).when(mA2dpNativeInterface).setActiveDevice(any(BluetoothDevice.class)); 236 connectDevice(mTestDevice); 237 Assert.assertTrue(mA2dpService.setActiveDevice(mTestDevice)); 238 verify(mA2dpNativeInterface).setActiveDevice(mTestDevice); 239 // A2DP Service is already running: test stop(). Note: must be done on the main thread. 240 InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { 241 public void run() { 242 Assert.assertTrue(mA2dpService.stop()); 243 } 244 }); 245 // Verify that setActiveDevice(null) was called during shutdown 246 verify(mA2dpNativeInterface).setActiveDevice(null); 247 // Try to restart the service. Note: must be done on the main thread. 248 InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { 249 public void run() { 250 Assert.assertTrue(mA2dpService.start()); 251 } 252 }); 253 } 254 255 /** 256 * Test get/set priority for BluetoothDevice 257 */ 258 @Test 259 public void testGetSetPriority() { 260 Assert.assertEquals("Initial device priority", 261 BluetoothProfile.PRIORITY_UNDEFINED, 262 mA2dpService.getPriority(mTestDevice)); 263 264 Assert.assertTrue(mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_OFF)); 265 Assert.assertEquals("Setting device priority to PRIORITY_OFF", 266 BluetoothProfile.PRIORITY_OFF, 267 mA2dpService.getPriority(mTestDevice)); 268 269 Assert.assertTrue(mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON)); 270 Assert.assertEquals("Setting device priority to PRIORITY_ON", 271 BluetoothProfile.PRIORITY_ON, 272 mA2dpService.getPriority(mTestDevice)); 273 274 Assert.assertTrue(mA2dpService.setPriority(mTestDevice, 275 BluetoothProfile.PRIORITY_AUTO_CONNECT)); 276 Assert.assertEquals("Setting device priority to PRIORITY_AUTO_CONNECT", 277 BluetoothProfile.PRIORITY_AUTO_CONNECT, 278 mA2dpService.getPriority(mTestDevice)); 279 } 280 281 /** 282 * Test okToConnect method using various test cases 283 */ 284 @Test 285 public void testOkToConnect() { 286 int badPriorityValue = 1024; 287 int badBondState = 42; 288 testOkToConnectCase(mTestDevice, 289 BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_UNDEFINED, false); 290 testOkToConnectCase(mTestDevice, 291 BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_OFF, false); 292 testOkToConnectCase(mTestDevice, 293 BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_ON, false); 294 testOkToConnectCase(mTestDevice, 295 BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_AUTO_CONNECT, false); 296 testOkToConnectCase(mTestDevice, 297 BluetoothDevice.BOND_NONE, badPriorityValue, false); 298 testOkToConnectCase(mTestDevice, 299 BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_UNDEFINED, true); 300 testOkToConnectCase(mTestDevice, 301 BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_OFF, false); 302 testOkToConnectCase(mTestDevice, 303 BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_ON, true); 304 testOkToConnectCase(mTestDevice, 305 BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_AUTO_CONNECT, true); 306 testOkToConnectCase(mTestDevice, 307 BluetoothDevice.BOND_BONDING, badPriorityValue, false); 308 testOkToConnectCase(mTestDevice, 309 BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_UNDEFINED, true); 310 testOkToConnectCase(mTestDevice, 311 BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_OFF, false); 312 testOkToConnectCase(mTestDevice, 313 BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_ON, true); 314 testOkToConnectCase(mTestDevice, 315 BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_AUTO_CONNECT, true); 316 testOkToConnectCase(mTestDevice, 317 BluetoothDevice.BOND_BONDED, badPriorityValue, false); 318 testOkToConnectCase(mTestDevice, 319 badBondState, BluetoothProfile.PRIORITY_UNDEFINED, false); 320 testOkToConnectCase(mTestDevice, 321 badBondState, BluetoothProfile.PRIORITY_OFF, false); 322 testOkToConnectCase(mTestDevice, 323 badBondState, BluetoothProfile.PRIORITY_ON, false); 324 testOkToConnectCase(mTestDevice, 325 badBondState, BluetoothProfile.PRIORITY_AUTO_CONNECT, false); 326 testOkToConnectCase(mTestDevice, 327 badBondState, badPriorityValue, false); 328 // Restore prirority to undefined for this test device 329 Assert.assertTrue(mA2dpService.setPriority( 330 mTestDevice, BluetoothProfile.PRIORITY_UNDEFINED)); 331 } 332 333 334 /** 335 * Test that an outgoing connection to device that does not have A2DP Sink UUID is rejected 336 */ 337 @Test 338 public void testOutgoingConnectMissingAudioSinkUuid() { 339 // Update the device priority so okToConnect() returns true 340 mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON); 341 doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class)); 342 doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); 343 344 // Return AudioSource UUID instead of AudioSink 345 doReturn(new ParcelUuid[]{BluetoothUuid.AudioSource}).when(mAdapterService) 346 .getRemoteUuids(any(BluetoothDevice.class)); 347 348 // Send a connect request 349 Assert.assertFalse("Connect expected to fail", mA2dpService.connect(mTestDevice)); 350 } 351 352 /** 353 * Test that an outgoing connection to device with PRIORITY_OFF is rejected 354 */ 355 @Test 356 public void testOutgoingConnectPriorityOff() { 357 doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class)); 358 doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); 359 360 // Set the device priority to PRIORITY_OFF so connect() should fail 361 mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_OFF); 362 363 // Send a connect request 364 Assert.assertFalse("Connect expected to fail", mA2dpService.connect(mTestDevice)); 365 } 366 367 /** 368 * Test that an outgoing connection times out 369 */ 370 @Test 371 public void testOutgoingConnectTimeout() { 372 // Update the device priority so okToConnect() returns true 373 mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON); 374 doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class)); 375 doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); 376 377 // Send a connect request 378 Assert.assertTrue("Connect failed", mA2dpService.connect(mTestDevice)); 379 380 // Verify the connection state broadcast, and that we are in Connecting state 381 verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_CONNECTING, 382 BluetoothProfile.STATE_DISCONNECTED); 383 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 384 mA2dpService.getConnectionState(mTestDevice)); 385 386 // Verify the connection state broadcast, and that we are in Disconnected state 387 verifyConnectionStateIntent(A2dpStateMachine.sConnectTimeoutMs * 2, 388 mTestDevice, BluetoothProfile.STATE_DISCONNECTED, 389 BluetoothProfile.STATE_CONNECTING); 390 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 391 mA2dpService.getConnectionState(mTestDevice)); 392 } 393 394 /** 395 * Test that an outgoing connection/disconnection succeeds 396 */ 397 @Test 398 public void testOutgoingConnectDisconnectSuccess() { 399 A2dpStackEvent connCompletedEvent; 400 401 // Update the device priority so okToConnect() returns true 402 mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON); 403 doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class)); 404 doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); 405 406 // Send a connect request 407 Assert.assertTrue("Connect failed", mA2dpService.connect(mTestDevice)); 408 409 // Verify the connection state broadcast, and that we are in Connecting state 410 verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_CONNECTING, 411 BluetoothProfile.STATE_DISCONNECTED); 412 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 413 mA2dpService.getConnectionState(mTestDevice)); 414 415 // Send a message to trigger connection completed 416 connCompletedEvent = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 417 connCompletedEvent.device = mTestDevice; 418 connCompletedEvent.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED; 419 mA2dpService.messageFromNative(connCompletedEvent); 420 421 // Verify the connection state broadcast, and that we are in Connected state 422 verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_CONNECTED, 423 BluetoothProfile.STATE_CONNECTING); 424 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 425 mA2dpService.getConnectionState(mTestDevice)); 426 427 // Verify the list of connected devices 428 Assert.assertTrue(mA2dpService.getConnectedDevices().contains(mTestDevice)); 429 430 // Send a disconnect request 431 Assert.assertTrue("Disconnect failed", mA2dpService.disconnect(mTestDevice)); 432 433 // Verify the connection state broadcast, and that we are in Disconnecting state 434 verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_DISCONNECTING, 435 BluetoothProfile.STATE_CONNECTED); 436 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING, 437 mA2dpService.getConnectionState(mTestDevice)); 438 439 // Send a message to trigger disconnection completed 440 connCompletedEvent = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 441 connCompletedEvent.device = mTestDevice; 442 connCompletedEvent.valueInt = A2dpStackEvent.CONNECTION_STATE_DISCONNECTED; 443 mA2dpService.messageFromNative(connCompletedEvent); 444 445 // Verify the connection state broadcast, and that we are in Disconnected state 446 verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_DISCONNECTED, 447 BluetoothProfile.STATE_DISCONNECTING); 448 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 449 mA2dpService.getConnectionState(mTestDevice)); 450 451 // Verify the list of connected devices 452 Assert.assertFalse(mA2dpService.getConnectedDevices().contains(mTestDevice)); 453 } 454 455 /** 456 * Test that an outgoing connection/disconnection succeeds 457 */ 458 @Test 459 public void testMaxConnectDevices() { 460 A2dpStackEvent connCompletedEvent; 461 BluetoothDevice[] testDevices = new BluetoothDevice[MAX_CONNECTED_AUDIO_DEVICES]; 462 BluetoothDevice extraTestDevice; 463 464 doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class)); 465 doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); 466 467 // Prepare and connect all test devices 468 for (int i = 0; i < MAX_CONNECTED_AUDIO_DEVICES; i++) { 469 BluetoothDevice testDevice = TestUtils.getTestDevice(mAdapter, i); 470 testDevices[i] = testDevice; 471 mA2dpService.setPriority(testDevice, BluetoothProfile.PRIORITY_ON); 472 // Send a connect request 473 Assert.assertTrue("Connect failed", mA2dpService.connect(testDevice)); 474 // Verify the connection state broadcast, and that we are in Connecting state 475 verifyConnectionStateIntent(TIMEOUT_MS, testDevice, BluetoothProfile.STATE_CONNECTING, 476 BluetoothProfile.STATE_DISCONNECTED); 477 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 478 mA2dpService.getConnectionState(testDevice)); 479 // Send a message to trigger connection completed 480 connCompletedEvent = 481 new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 482 connCompletedEvent.device = testDevice; 483 connCompletedEvent.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED; 484 mA2dpService.messageFromNative(connCompletedEvent); 485 486 // Verify the connection state broadcast, and that we are in Connected state 487 verifyConnectionStateIntent(TIMEOUT_MS, testDevice, BluetoothProfile.STATE_CONNECTED, 488 BluetoothProfile.STATE_CONNECTING); 489 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 490 mA2dpService.getConnectionState(testDevice)); 491 // Verify the list of connected devices 492 Assert.assertTrue(mA2dpService.getConnectedDevices().contains(testDevice)); 493 } 494 495 // Prepare and connect the extra test device. The connect request should fail 496 extraTestDevice = TestUtils.getTestDevice(mAdapter, MAX_CONNECTED_AUDIO_DEVICES); 497 mA2dpService.setPriority(extraTestDevice, BluetoothProfile.PRIORITY_ON); 498 // Send a connect request 499 Assert.assertFalse("Connect expected to fail", mA2dpService.connect(extraTestDevice)); 500 } 501 502 /** 503 * Test that only CONNECTION_STATE_CONNECTED or CONNECTION_STATE_CONNECTING A2DP stack events 504 * will create a state machine. 505 */ 506 @Test 507 public void testCreateStateMachineStackEvents() { 508 A2dpStackEvent stackEvent; 509 510 // Update the device priority so okToConnect() returns true 511 mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON); 512 doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class)); 513 doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); 514 515 // A2DP stack event: CONNECTION_STATE_CONNECTING - state machine should be created 516 generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTING, 517 BluetoothProfile.STATE_DISCONNECTED); 518 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 519 mA2dpService.getConnectionState(mTestDevice)); 520 Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); 521 522 // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed 523 generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED, 524 BluetoothProfile.STATE_CONNECTING); 525 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 526 mA2dpService.getConnectionState(mTestDevice)); 527 Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); 528 mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE); 529 Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice)); 530 531 // A2DP stack event: CONNECTION_STATE_CONNECTED - state machine should be created 532 generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTED, 533 BluetoothProfile.STATE_DISCONNECTED); 534 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 535 mA2dpService.getConnectionState(mTestDevice)); 536 Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); 537 538 // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed 539 generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED, 540 BluetoothProfile.STATE_CONNECTED); 541 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 542 mA2dpService.getConnectionState(mTestDevice)); 543 Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); 544 mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE); 545 Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice)); 546 547 // A2DP stack event: CONNECTION_STATE_DISCONNECTING - state machine should not be created 548 generateUnexpectedConnectionMessageFromNative(mTestDevice, 549 BluetoothProfile.STATE_DISCONNECTING, 550 BluetoothProfile.STATE_DISCONNECTED); 551 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 552 mA2dpService.getConnectionState(mTestDevice)); 553 Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice)); 554 555 // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine should not be created 556 generateUnexpectedConnectionMessageFromNative(mTestDevice, 557 BluetoothProfile.STATE_DISCONNECTED, 558 BluetoothProfile.STATE_DISCONNECTED); 559 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 560 mA2dpService.getConnectionState(mTestDevice)); 561 Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice)); 562 } 563 564 /** 565 * Test that EVENT_TYPE_AUDIO_STATE_CHANGED and EVENT_TYPE_CODEC_CONFIG_CHANGED events 566 * are processed. 567 */ 568 @Test 569 public void testProcessAudioStateChangedCodecConfigChangedEvents() { 570 A2dpStackEvent stackEvent; 571 BluetoothCodecConfig codecConfigSbc = 572 new BluetoothCodecConfig( 573 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, 574 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, 575 BluetoothCodecConfig.SAMPLE_RATE_44100, 576 BluetoothCodecConfig.BITS_PER_SAMPLE_16, 577 BluetoothCodecConfig.CHANNEL_MODE_STEREO, 578 0, 0, 0, 0); // Codec-specific fields 579 BluetoothCodecConfig codecConfig = codecConfigSbc; 580 BluetoothCodecConfig[] codecsLocalCapabilities = new BluetoothCodecConfig[1]; 581 BluetoothCodecConfig[] codecsSelectableCapabilities = new BluetoothCodecConfig[1]; 582 codecsLocalCapabilities[0] = codecConfigSbc; 583 codecsSelectableCapabilities[0] = codecConfigSbc; 584 BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(codecConfig, 585 codecsLocalCapabilities, 586 codecsSelectableCapabilities); 587 588 // Update the device priority so okToConnect() returns true 589 mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON); 590 doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class)); 591 doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); 592 593 // A2DP stack event: EVENT_TYPE_AUDIO_STATE_CHANGED - state machine should not be created 594 generateUnexpectedAudioMessageFromNative(mTestDevice, A2dpStackEvent.AUDIO_STATE_STARTED, 595 BluetoothA2dp.STATE_PLAYING, 596 BluetoothA2dp.STATE_NOT_PLAYING); 597 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 598 mA2dpService.getConnectionState(mTestDevice)); 599 Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice)); 600 601 // A2DP stack event: EVENT_TYPE_CODEC_CONFIG_CHANGED - state machine should not be created 602 generateUnexpectedCodecMessageFromNative(mTestDevice, codecStatus); 603 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 604 mA2dpService.getConnectionState(mTestDevice)); 605 Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice)); 606 607 // A2DP stack event: CONNECTION_STATE_CONNECTED - state machine should be created 608 generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTED, 609 BluetoothProfile.STATE_DISCONNECTED); 610 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 611 mA2dpService.getConnectionState(mTestDevice)); 612 Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); 613 614 // A2DP stack event: EVENT_TYPE_AUDIO_STATE_CHANGED - Intent broadcast should be generated 615 // NOTE: The first message (STATE_PLAYING -> STATE_NOT_PLAYING) is generated internally 616 // by the state machine when Connected, and needs to be extracted first before generating 617 // the actual message from native. 618 verifyAudioStateIntent(TIMEOUT_MS, mTestDevice, BluetoothA2dp.STATE_NOT_PLAYING, 619 BluetoothA2dp.STATE_PLAYING); 620 generateAudioMessageFromNative(mTestDevice, 621 A2dpStackEvent.AUDIO_STATE_STARTED, 622 BluetoothA2dp.STATE_PLAYING, 623 BluetoothA2dp.STATE_NOT_PLAYING); 624 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 625 mA2dpService.getConnectionState(mTestDevice)); 626 Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); 627 628 // A2DP stack event: EVENT_TYPE_CODEC_CONFIG_CHANGED - Intent broadcast should be generated 629 generateCodecMessageFromNative(mTestDevice, codecStatus); 630 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 631 mA2dpService.getConnectionState(mTestDevice)); 632 Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); 633 634 // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed 635 generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED, 636 BluetoothProfile.STATE_CONNECTED); 637 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 638 mA2dpService.getConnectionState(mTestDevice)); 639 Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); 640 mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE); 641 Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice)); 642 } 643 644 /** 645 * Test that a state machine in DISCONNECTED state is removed only after the device is unbond. 646 */ 647 @Test 648 public void testDeleteStateMachineUnbondEvents() { 649 A2dpStackEvent stackEvent; 650 651 // Update the device priority so okToConnect() returns true 652 mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON); 653 doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class)); 654 doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); 655 656 // A2DP stack event: CONNECTION_STATE_CONNECTING - state machine should be created 657 generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTING, 658 BluetoothProfile.STATE_DISCONNECTED); 659 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 660 mA2dpService.getConnectionState(mTestDevice)); 661 Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); 662 // Device unbond - state machine is not removed 663 mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE); 664 Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); 665 666 // A2DP stack event: CONNECTION_STATE_CONNECTED - state machine is not removed 667 mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_BONDED); 668 generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTED, 669 BluetoothProfile.STATE_CONNECTING); 670 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 671 mA2dpService.getConnectionState(mTestDevice)); 672 Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); 673 // Device unbond - state machine is not removed 674 mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE); 675 Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); 676 677 // A2DP stack event: CONNECTION_STATE_DISCONNECTING - state machine is not removed 678 mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_BONDED); 679 generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTING, 680 BluetoothProfile.STATE_CONNECTED); 681 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING, 682 mA2dpService.getConnectionState(mTestDevice)); 683 Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); 684 // Device unbond - state machine is not removed 685 mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE); 686 Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); 687 688 // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine is not removed 689 mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_BONDED); 690 generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED, 691 BluetoothProfile.STATE_DISCONNECTING); 692 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 693 mA2dpService.getConnectionState(mTestDevice)); 694 Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); 695 // Device unbond - state machine is removed 696 mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE); 697 Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice)); 698 } 699 700 /** 701 * Test that a CONNECTION_STATE_DISCONNECTED A2DP stack event will remove the state machine 702 * only if the device is unbond. 703 */ 704 @Test 705 public void testDeleteStateMachineDisconnectEvents() { 706 A2dpStackEvent stackEvent; 707 708 // Update the device priority so okToConnect() returns true 709 mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON); 710 doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class)); 711 doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); 712 713 // A2DP stack event: CONNECTION_STATE_CONNECTING - state machine should be created 714 generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTING, 715 BluetoothProfile.STATE_DISCONNECTED); 716 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 717 mA2dpService.getConnectionState(mTestDevice)); 718 Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); 719 720 // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine is not removed 721 generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED, 722 BluetoothProfile.STATE_CONNECTING); 723 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 724 mA2dpService.getConnectionState(mTestDevice)); 725 Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); 726 727 // A2DP stack event: CONNECTION_STATE_CONNECTING - state machine remains 728 generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTING, 729 BluetoothProfile.STATE_DISCONNECTED); 730 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 731 mA2dpService.getConnectionState(mTestDevice)); 732 Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); 733 734 // Device bond state marked as unbond - state machine is not removed 735 doReturn(BluetoothDevice.BOND_NONE).when(mAdapterService) 736 .getBondState(any(BluetoothDevice.class)); 737 Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); 738 739 // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine is removed 740 generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED, 741 BluetoothProfile.STATE_CONNECTING); 742 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 743 mA2dpService.getConnectionState(mTestDevice)); 744 Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice)); 745 } 746 747 private void connectDevice(BluetoothDevice device) { 748 A2dpStackEvent connCompletedEvent; 749 750 List<BluetoothDevice> prevConnectedDevices = mA2dpService.getConnectedDevices(); 751 752 // Update the device priority so okToConnect() returns true 753 mA2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON); 754 doReturn(true).when(mA2dpNativeInterface).connectA2dp(device); 755 doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(device); 756 757 // Send a connect request 758 Assert.assertTrue("Connect failed", mA2dpService.connect(device)); 759 760 // Verify the connection state broadcast, and that we are in Connecting state 761 verifyConnectionStateIntent(TIMEOUT_MS, device, BluetoothProfile.STATE_CONNECTING, 762 BluetoothProfile.STATE_DISCONNECTED); 763 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 764 mA2dpService.getConnectionState(device)); 765 766 // Send a message to trigger connection completed 767 connCompletedEvent = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 768 connCompletedEvent.device = device; 769 connCompletedEvent.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED; 770 mA2dpService.messageFromNative(connCompletedEvent); 771 772 // Verify the connection state broadcast, and that we are in Connected state 773 verifyConnectionStateIntent(TIMEOUT_MS, device, BluetoothProfile.STATE_CONNECTED, 774 BluetoothProfile.STATE_CONNECTING); 775 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 776 mA2dpService.getConnectionState(device)); 777 778 // Verify that the device is in the list of connected devices 779 Assert.assertTrue(mA2dpService.getConnectedDevices().contains(device)); 780 // Verify the list of previously connected devices 781 for (BluetoothDevice prevDevice : prevConnectedDevices) { 782 Assert.assertTrue(mA2dpService.getConnectedDevices().contains(prevDevice)); 783 } 784 } 785 786 private void generateConnectionMessageFromNative(BluetoothDevice device, int newConnectionState, 787 int oldConnectionState) { 788 A2dpStackEvent stackEvent = 789 new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 790 stackEvent.device = device; 791 stackEvent.valueInt = newConnectionState; 792 mA2dpService.messageFromNative(stackEvent); 793 // Verify the connection state broadcast 794 verifyConnectionStateIntent(TIMEOUT_MS, device, newConnectionState, oldConnectionState); 795 } 796 797 private void generateUnexpectedConnectionMessageFromNative(BluetoothDevice device, 798 int newConnectionState, 799 int oldConnectionState) { 800 A2dpStackEvent stackEvent = 801 new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 802 stackEvent.device = device; 803 stackEvent.valueInt = newConnectionState; 804 mA2dpService.messageFromNative(stackEvent); 805 // Verify the connection state broadcast 806 verifyNoConnectionStateIntent(TIMEOUT_MS); 807 } 808 809 private void generateAudioMessageFromNative(BluetoothDevice device, int audioStackEvent, 810 int newAudioState, int oldAudioState) { 811 A2dpStackEvent stackEvent = 812 new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED); 813 stackEvent.device = device; 814 stackEvent.valueInt = audioStackEvent; 815 mA2dpService.messageFromNative(stackEvent); 816 // Verify the audio state broadcast 817 verifyAudioStateIntent(TIMEOUT_MS, device, newAudioState, oldAudioState); 818 } 819 820 private void generateUnexpectedAudioMessageFromNative(BluetoothDevice device, 821 int audioStackEvent, int newAudioState, 822 int oldAudioState) { 823 A2dpStackEvent stackEvent = 824 new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED); 825 stackEvent.device = device; 826 stackEvent.valueInt = audioStackEvent; 827 mA2dpService.messageFromNative(stackEvent); 828 // Verify the audio state broadcast 829 verifyNoAudioStateIntent(TIMEOUT_MS); 830 } 831 832 private void generateCodecMessageFromNative(BluetoothDevice device, 833 BluetoothCodecStatus codecStatus) { 834 A2dpStackEvent stackEvent = 835 new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED); 836 stackEvent.device = device; 837 stackEvent.codecStatus = codecStatus; 838 mA2dpService.messageFromNative(stackEvent); 839 // Verify the codec status broadcast 840 verifyCodecConfigIntent(TIMEOUT_MS, device, codecStatus); 841 } 842 843 private void generateUnexpectedCodecMessageFromNative(BluetoothDevice device, 844 BluetoothCodecStatus codecStatus) { 845 A2dpStackEvent stackEvent = 846 new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED); 847 stackEvent.device = device; 848 stackEvent.codecStatus = codecStatus; 849 mA2dpService.messageFromNative(stackEvent); 850 // Verify the codec status broadcast 851 verifyNoCodecConfigIntent(TIMEOUT_MS); 852 } 853 854 /** 855 * Helper function to test okToConnect() method. 856 * 857 * @param device test device 858 * @param bondState bond state value, could be invalid 859 * @param priority value, could be invalid, coudl be invalid 860 * @param expected expected result from okToConnect() 861 */ 862 private void testOkToConnectCase(BluetoothDevice device, int bondState, int priority, 863 boolean expected) { 864 doReturn(bondState).when(mAdapterService).getBondState(device); 865 Assert.assertTrue(mA2dpService.setPriority(device, priority)); 866 867 // Test when the AdapterService is in non-quiet mode: the result should not depend 868 // on whether the connection request is outgoing or incoming. 869 doReturn(false).when(mAdapterService).isQuietModeEnabled(); 870 Assert.assertEquals(expected, mA2dpService.okToConnect(device, true)); // Outgoing 871 Assert.assertEquals(expected, mA2dpService.okToConnect(device, false)); // Incoming 872 873 // Test when the AdapterService is in quiet mode: the result should always be 874 // false when the connection request is incoming. 875 doReturn(true).when(mAdapterService).isQuietModeEnabled(); 876 Assert.assertEquals(expected, mA2dpService.okToConnect(device, true)); // Outgoing 877 Assert.assertEquals(false, mA2dpService.okToConnect(device, false)); // Incoming 878 } 879 } 880