1 /* 2 * Copyright 2020 HIMSA II K/S - www.himsa.com. 3 * Represented by EHIMA - www.ehima.com 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.bluetooth.vc; 19 20 import static org.mockito.Mockito.*; 21 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothDevice; 24 import android.bluetooth.BluetoothProfile; 25 import android.bluetooth.BluetoothUuid; 26 import android.bluetooth.BluetoothVolumeControl; 27 import android.bluetooth.IBluetoothVolumeControlCallback; 28 import android.content.AttributionSource; 29 import android.content.BroadcastReceiver; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.IntentFilter; 33 import android.media.AudioManager; 34 import android.os.Binder; 35 import android.os.Looper; 36 import android.os.ParcelUuid; 37 38 import androidx.test.InstrumentationRegistry; 39 import androidx.test.filters.MediumTest; 40 import androidx.test.rule.ServiceTestRule; 41 import androidx.test.runner.AndroidJUnit4; 42 43 import com.android.bluetooth.btservice.AdapterService; 44 import com.android.bluetooth.btservice.ServiceFactory; 45 import com.android.bluetooth.btservice.storage.DatabaseManager; 46 import com.android.bluetooth.csip.CsipSetCoordinatorService; 47 import com.android.bluetooth.R; 48 import com.android.bluetooth.TestUtils; 49 import com.android.bluetooth.x.com.android.modules.utils.SynchronousResultReceiver; 50 51 import org.junit.After; 52 import org.junit.Assert; 53 import org.junit.Assume; 54 import org.junit.Before; 55 import org.junit.Rule; 56 import org.junit.Test; 57 import org.junit.runner.RunWith; 58 import org.mockito.Mock; 59 import org.mockito.Mockito; 60 import org.mockito.MockitoAnnotations; 61 62 import java.time.Duration; 63 import java.util.HashMap; 64 import java.util.List; 65 import java.util.concurrent.LinkedBlockingQueue; 66 import java.util.concurrent.TimeoutException; 67 import java.util.stream.IntStream; 68 69 @MediumTest 70 @RunWith(AndroidJUnit4.class) 71 public class VolumeControlServiceTest { 72 private BluetoothAdapter mAdapter; 73 private AttributionSource mAttributionSource; 74 private Context mTargetContext; 75 private VolumeControlService mService; 76 private VolumeControlService.BluetoothVolumeControlBinder mServiceBinder; 77 private BluetoothDevice mDevice; 78 private BluetoothDevice mDeviceTwo; 79 private HashMap<BluetoothDevice, LinkedBlockingQueue<Intent>> mDeviceQueueMap; 80 private static final int TIMEOUT_MS = 1000; 81 private static final int BT_LE_AUDIO_MAX_VOL = 255; 82 private static final int MEDIA_MIN_VOL = 0; 83 private static final int MEDIA_MAX_VOL = 25; 84 private static final int CALL_MIN_VOL = 1; 85 private static final int CALL_MAX_VOL = 8; 86 87 private BroadcastReceiver mVolumeControlIntentReceiver; 88 89 @Mock private AdapterService mAdapterService; 90 @Mock private DatabaseManager mDatabaseManager; 91 @Mock private VolumeControlNativeInterface mNativeInterface; 92 @Mock private AudioManager mAudioManager; 93 @Mock private ServiceFactory mServiceFactory; 94 @Mock private CsipSetCoordinatorService mCsipService; 95 96 @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); 97 98 @Before setUp()99 public void setUp() throws Exception { 100 mTargetContext = InstrumentationRegistry.getTargetContext(); 101 // Set up mocks and test assets 102 MockitoAnnotations.initMocks(this); 103 104 if (Looper.myLooper() == null) { 105 Looper.prepare(); 106 } 107 108 TestUtils.setAdapterService(mAdapterService); 109 doReturn(mDatabaseManager).when(mAdapterService).getDatabase(); 110 doReturn(true, false).when(mAdapterService).isStartedProfile(anyString()); 111 112 mAdapter = BluetoothAdapter.getDefaultAdapter(); 113 mAttributionSource = mAdapter.getAttributionSource(); 114 115 doReturn(MEDIA_MIN_VOL).when(mAudioManager) 116 .getStreamMinVolume(eq(AudioManager.STREAM_MUSIC)); 117 doReturn(MEDIA_MAX_VOL).when(mAudioManager) 118 .getStreamMaxVolume(eq(AudioManager.STREAM_MUSIC)); 119 doReturn(CALL_MIN_VOL).when(mAudioManager) 120 .getStreamMinVolume(eq(AudioManager.STREAM_VOICE_CALL)); 121 doReturn(CALL_MAX_VOL).when(mAudioManager) 122 .getStreamMaxVolume(eq(AudioManager.STREAM_VOICE_CALL)); 123 124 startService(); 125 mService.mVolumeControlNativeInterface = mNativeInterface; 126 mService.mAudioManager = mAudioManager; 127 mService.mFactory = mServiceFactory; 128 mServiceBinder = (VolumeControlService.BluetoothVolumeControlBinder) mService.initBinder(); 129 mServiceBinder.mIsTesting = true; 130 131 doReturn(mCsipService).when(mServiceFactory).getCsipSetCoordinatorService(); 132 133 // Override the timeout value to speed up the test 134 VolumeControlStateMachine.sConnectTimeoutMs = TIMEOUT_MS; // 1s 135 136 // Set up the Connection State Changed receiver 137 IntentFilter filter = new IntentFilter(); 138 filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 139 filter.addAction(BluetoothVolumeControl.ACTION_CONNECTION_STATE_CHANGED); 140 141 mVolumeControlIntentReceiver = new VolumeControlIntentReceiver(); 142 mTargetContext.registerReceiver(mVolumeControlIntentReceiver, filter); 143 144 // Get a device for testing 145 mDevice = TestUtils.getTestDevice(mAdapter, 0); 146 mDeviceTwo = TestUtils.getTestDevice(mAdapter, 1); 147 mDeviceQueueMap = new HashMap<>(); 148 mDeviceQueueMap.put(mDevice, new LinkedBlockingQueue<>()); 149 mDeviceQueueMap.put(mDeviceTwo, new LinkedBlockingQueue<>()); 150 doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService) 151 .getBondState(any(BluetoothDevice.class)); 152 doReturn(new ParcelUuid[]{BluetoothUuid.VOLUME_CONTROL}).when(mAdapterService) 153 .getRemoteUuids(any(BluetoothDevice.class)); 154 } 155 156 @After tearDown()157 public void tearDown() throws Exception { 158 if (mService == null) { 159 return; 160 } 161 162 stopService(); 163 mTargetContext.unregisterReceiver(mVolumeControlIntentReceiver); 164 mDeviceQueueMap.clear(); 165 TestUtils.clearAdapterService(mAdapterService); 166 reset(mAudioManager); 167 } 168 startService()169 private void startService() throws TimeoutException { 170 TestUtils.startService(mServiceRule, VolumeControlService.class); 171 mService = VolumeControlService.getVolumeControlService(); 172 Assert.assertNotNull(mService); 173 } 174 stopService()175 private void stopService() throws TimeoutException { 176 TestUtils.stopService(mServiceRule, VolumeControlService.class); 177 mService = VolumeControlService.getVolumeControlService(); 178 Assert.assertNull(mService); 179 } 180 181 private class VolumeControlIntentReceiver extends BroadcastReceiver { 182 @Override onReceive(Context context, Intent intent)183 public void onReceive(Context context, Intent intent) { 184 try { 185 BluetoothDevice device = intent.getParcelableExtra( 186 BluetoothDevice.EXTRA_DEVICE); 187 Assert.assertNotNull(device); 188 LinkedBlockingQueue<Intent> queue = mDeviceQueueMap.get(device); 189 Assert.assertNotNull(queue); 190 queue.put(intent); 191 } catch (InterruptedException e) { 192 Assert.fail("Cannot add Intent to the Connection State queue: " 193 + e.getMessage()); 194 } 195 } 196 } 197 verifyConnectionStateIntent(int timeoutMs, BluetoothDevice device, int newState, int prevState)198 private void verifyConnectionStateIntent(int timeoutMs, BluetoothDevice device, 199 int newState, int prevState) { 200 Intent intent = TestUtils.waitForIntent(timeoutMs, mDeviceQueueMap.get(device)); 201 Assert.assertNotNull(intent); 202 Assert.assertEquals(BluetoothVolumeControl.ACTION_CONNECTION_STATE_CHANGED, 203 intent.getAction()); 204 Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); 205 Assert.assertEquals(newState, intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); 206 Assert.assertEquals(prevState, intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 207 -1)); 208 } 209 verifyNoConnectionStateIntent(int timeoutMs, BluetoothDevice device)210 private void verifyNoConnectionStateIntent(int timeoutMs, BluetoothDevice device) { 211 Intent intent = TestUtils.waitForNoIntent(timeoutMs, mDeviceQueueMap.get(device)); 212 Assert.assertNull(intent); 213 } 214 215 /** 216 * Test getting VolumeControl Service: getVolumeControlService() 217 */ 218 @Test testGetVolumeControlService()219 public void testGetVolumeControlService() { 220 Assert.assertEquals(mService, VolumeControlService.getVolumeControlService()); 221 } 222 223 /** 224 * Test stop VolumeControl Service 225 */ 226 @Test testStopVolumeControlService()227 public void testStopVolumeControlService() throws Exception { 228 // Prepare: connect 229 connectDevice(mDevice); 230 // VolumeControl Service is already running: test stop(). 231 // Note: must be done on the main thread 232 InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { 233 public void run() { 234 Assert.assertTrue(mService.stop()); 235 } 236 }); 237 // Try to restart the service. Note: must be done on the main thread 238 InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { 239 public void run() { 240 Assert.assertTrue(mService.start()); 241 } 242 }); 243 } 244 245 /** 246 * Test get/set policy for BluetoothDevice 247 */ 248 @Test testGetSetPolicy()249 public void testGetSetPolicy() { 250 when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); 251 when(mDatabaseManager 252 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL)) 253 .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 254 Assert.assertEquals("Initial device policy", 255 BluetoothProfile.CONNECTION_POLICY_UNKNOWN, 256 mService.getConnectionPolicy(mDevice)); 257 258 when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); 259 when(mDatabaseManager 260 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL)) 261 .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); 262 Assert.assertEquals("Setting device policy to POLICY_FORBIDDEN", 263 BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, 264 mService.getConnectionPolicy(mDevice)); 265 266 when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); 267 when(mDatabaseManager 268 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL)) 269 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); 270 Assert.assertEquals("Setting device policy to POLICY_ALLOWED", 271 BluetoothProfile.CONNECTION_POLICY_ALLOWED, 272 mService.getConnectionPolicy(mDevice)); 273 } 274 275 /** 276 * Test if getProfileConnectionPolicy works after the service is stopped. 277 */ 278 @Test testGetPolicyAfterStopped()279 public void testGetPolicyAfterStopped() throws Exception { 280 mService.stop(); 281 when(mDatabaseManager 282 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL)) 283 .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 284 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get(); 285 int defaultRecvValue = -1000; 286 mServiceBinder.getConnectionPolicy(mDevice, mAttributionSource, recv); 287 int policy = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)) 288 .getValue(defaultRecvValue); 289 Assert.assertEquals("Initial device policy", 290 BluetoothProfile.CONNECTION_POLICY_UNKNOWN, policy); 291 } 292 293 /** 294 * Test okToConnect method using various test cases 295 */ 296 @Test testOkToConnect()297 public void testOkToConnect() { 298 int badPolicyValue = 1024; 299 int badBondState = 42; 300 testOkToConnectCase(mDevice, 301 BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false); 302 testOkToConnectCase(mDevice, 303 BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false); 304 testOkToConnectCase(mDevice, 305 BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false); 306 testOkToConnectCase(mDevice, 307 BluetoothDevice.BOND_NONE, badPolicyValue, false); 308 testOkToConnectCase(mDevice, 309 BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false); 310 testOkToConnectCase(mDevice, 311 BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false); 312 testOkToConnectCase(mDevice, 313 BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false); 314 testOkToConnectCase(mDevice, 315 BluetoothDevice.BOND_BONDING, badPolicyValue, false); 316 testOkToConnectCase(mDevice, 317 BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true); 318 testOkToConnectCase(mDevice, 319 BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false); 320 testOkToConnectCase(mDevice, 321 BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_ALLOWED, true); 322 testOkToConnectCase(mDevice, 323 BluetoothDevice.BOND_BONDED, badPolicyValue, false); 324 testOkToConnectCase(mDevice, 325 badBondState, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false); 326 testOkToConnectCase(mDevice, 327 badBondState, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false); 328 testOkToConnectCase(mDevice, 329 badBondState, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false); 330 testOkToConnectCase(mDevice, 331 badBondState, badPolicyValue, false); 332 } 333 334 /** 335 * Test that an outgoing connection to device that does not have Volume Control UUID is rejected 336 */ 337 @Test testOutgoingConnectMissingVolumeControlUuid()338 public void testOutgoingConnectMissingVolumeControlUuid() { 339 // Update the device policy so okToConnect() returns true 340 when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); 341 when(mDatabaseManager 342 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL)) 343 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); 344 doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class)); 345 doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class)); 346 347 // Return No UUID 348 doReturn(new ParcelUuid[]{}).when(mAdapterService) 349 .getRemoteUuids(any(BluetoothDevice.class)); 350 351 // Send a connect request 352 Assert.assertFalse("Connect expected to fail", mService.connect(mDevice)); 353 } 354 355 /** 356 * Test that an outgoing connection to device that have Volume Control UUID is successful 357 */ 358 @Test testOutgoingConnectDisconnectExistingVolumeControlUuid()359 public void testOutgoingConnectDisconnectExistingVolumeControlUuid() throws Exception { 360 // Update the device policy so okToConnect() returns true 361 when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); 362 when(mDatabaseManager 363 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL)) 364 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); 365 doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class)); 366 doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class)); 367 368 // Return Volume Control UUID 369 doReturn(new ParcelUuid[]{BluetoothUuid.VOLUME_CONTROL}).when(mAdapterService) 370 .getRemoteUuids(any(BluetoothDevice.class)); 371 372 // Send a connect request via binder 373 SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 374 mServiceBinder.connect(mDevice, mAttributionSource, recv); 375 Assert.assertTrue("Connect expected to succeed", 376 recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)).getValue(false)); 377 378 // Verify the connection state broadcast, and that we are in Connecting state 379 verifyConnectionStateIntent(TIMEOUT_MS, mDevice, BluetoothProfile.STATE_CONNECTING, 380 BluetoothProfile.STATE_DISCONNECTED); 381 382 // Send a disconnect request via binder 383 recv = SynchronousResultReceiver.get(); 384 mServiceBinder.disconnect(mDevice, mAttributionSource, recv); 385 Assert.assertTrue("Disconnect expected to succeed", 386 recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)).getValue(false)); 387 388 // Verify the connection state broadcast, and that we are in Connecting state 389 verifyConnectionStateIntent(TIMEOUT_MS, mDevice, BluetoothProfile.STATE_DISCONNECTED, 390 BluetoothProfile.STATE_CONNECTING); 391 } 392 393 /** 394 * Test that an outgoing connection to device with POLICY_FORBIDDEN is rejected 395 */ 396 @Test testOutgoingConnectPolicyForbidden()397 public void testOutgoingConnectPolicyForbidden() { 398 doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class)); 399 doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class)); 400 401 // Set the device policy to POLICY_FORBIDDEN so connect() should fail 402 when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); 403 when(mDatabaseManager 404 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL)) 405 .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); 406 407 // Send a connect request 408 Assert.assertFalse("Connect expected to fail", mService.connect(mDevice)); 409 } 410 411 /** 412 * Test that an outgoing connection times out 413 */ 414 @Test testOutgoingConnectTimeout()415 public void testOutgoingConnectTimeout() throws Exception { 416 // Update the device policy so okToConnect() returns true 417 when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); 418 when(mDatabaseManager 419 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL)) 420 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); 421 doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class)); 422 doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class)); 423 424 // Send a connect request 425 Assert.assertTrue("Connect failed", mService.connect(mDevice)); 426 427 // Verify the connection state broadcast, and that we are in Connecting state 428 verifyConnectionStateIntent(TIMEOUT_MS, mDevice, BluetoothProfile.STATE_CONNECTING, 429 BluetoothProfile.STATE_DISCONNECTED); 430 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 431 mService.getConnectionState(mDevice)); 432 433 // Verify the connection state broadcast, and that we are in Disconnected state 434 verifyConnectionStateIntent(VolumeControlStateMachine.sConnectTimeoutMs * 2, 435 mDevice, BluetoothProfile.STATE_DISCONNECTED, 436 BluetoothProfile.STATE_CONNECTING); 437 438 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get(); 439 int defaultRecvValue = -1000; 440 mServiceBinder.getConnectionState(mDevice, mAttributionSource, recv); 441 int state = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)) 442 .getValue(defaultRecvValue); 443 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, state); 444 } 445 446 /** 447 * Test that only CONNECTION_STATE_CONNECTED or CONNECTION_STATE_CONNECTING Volume Control stack 448 * events will create a state machine. 449 */ 450 @Test testCreateStateMachineStackEvents()451 public void testCreateStateMachineStackEvents() { 452 // Update the device policy so okToConnect() returns true 453 when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); 454 when(mDatabaseManager 455 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL)) 456 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); 457 doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class)); 458 doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class)); 459 460 // stack event: CONNECTION_STATE_CONNECTING - state machine should be created 461 generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTING, 462 BluetoothProfile.STATE_DISCONNECTED); 463 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 464 mService.getConnectionState(mDevice)); 465 Assert.assertTrue(mService.getDevices().contains(mDevice)); 466 467 // stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed 468 generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_DISCONNECTED, 469 BluetoothProfile.STATE_CONNECTING); 470 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 471 mService.getConnectionState(mDevice)); 472 Assert.assertTrue(mService.getDevices().contains(mDevice)); 473 mService.bondStateChanged(mDevice, BluetoothDevice.BOND_NONE); 474 Assert.assertFalse(mService.getDevices().contains(mDevice)); 475 476 // stack event: CONNECTION_STATE_CONNECTED - state machine should be created 477 generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTED, 478 BluetoothProfile.STATE_DISCONNECTED); 479 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 480 mService.getConnectionState(mDevice)); 481 Assert.assertTrue(mService.getDevices().contains(mDevice)); 482 483 // stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed 484 generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_DISCONNECTED, 485 BluetoothProfile.STATE_CONNECTED); 486 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 487 mService.getConnectionState(mDevice)); 488 Assert.assertTrue(mService.getDevices().contains(mDevice)); 489 mService.bondStateChanged(mDevice, BluetoothDevice.BOND_NONE); 490 Assert.assertFalse(mService.getDevices().contains(mDevice)); 491 492 // stack event: CONNECTION_STATE_DISCONNECTING - state machine should not be created 493 generateUnexpectedConnectionMessageFromNative(mDevice, 494 BluetoothProfile.STATE_DISCONNECTING, 495 BluetoothProfile.STATE_DISCONNECTED); 496 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 497 mService.getConnectionState(mDevice)); 498 Assert.assertFalse(mService.getDevices().contains(mDevice)); 499 500 // stack event: CONNECTION_STATE_DISCONNECTED - state machine should not be created 501 generateUnexpectedConnectionMessageFromNative(mDevice, 502 BluetoothProfile.STATE_DISCONNECTED, 503 BluetoothProfile.STATE_DISCONNECTED); 504 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 505 mService.getConnectionState(mDevice)); 506 Assert.assertFalse(mService.getDevices().contains(mDevice)); 507 } 508 509 /** 510 * Test that a CONNECTION_STATE_DISCONNECTED Volume Control stack event will remove the state 511 * machine only if the device is unbond. 512 */ 513 @Test testDeleteStateMachineDisconnectEvents()514 public void testDeleteStateMachineDisconnectEvents() { 515 // Update the device policy so okToConnect() returns true 516 when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); 517 when(mDatabaseManager 518 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL)) 519 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); 520 doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class)); 521 doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class)); 522 523 // stack event: CONNECTION_STATE_CONNECTING - state machine should be created 524 generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTING, 525 BluetoothProfile.STATE_DISCONNECTED); 526 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 527 mService.getConnectionState(mDevice)); 528 Assert.assertTrue(mService.getDevices().contains(mDevice)); 529 530 // stack event: CONNECTION_STATE_DISCONNECTED - state machine is not removed 531 generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_DISCONNECTED, 532 BluetoothProfile.STATE_CONNECTING); 533 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 534 mService.getConnectionState(mDevice)); 535 Assert.assertTrue(mService.getDevices().contains(mDevice)); 536 537 // stack event: CONNECTION_STATE_CONNECTING - state machine remains 538 generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTING, 539 BluetoothProfile.STATE_DISCONNECTED); 540 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 541 mService.getConnectionState(mDevice)); 542 Assert.assertTrue(mService.getDevices().contains(mDevice)); 543 544 // device bond state marked as unbond - state machine is not removed 545 doReturn(BluetoothDevice.BOND_NONE).when(mAdapterService) 546 .getBondState(any(BluetoothDevice.class)); 547 Assert.assertTrue(mService.getDevices().contains(mDevice)); 548 549 // stack event: CONNECTION_STATE_DISCONNECTED - state machine is removed 550 generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_DISCONNECTED, 551 BluetoothProfile.STATE_CONNECTING); 552 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 553 mService.getConnectionState(mDevice)); 554 Assert.assertFalse(mService.getDevices().contains(mDevice)); 555 } 556 557 /** 558 * Test that various Volume Control stack events will broadcast related states. 559 */ 560 @Test testVolumeControlStackEvents()561 public void testVolumeControlStackEvents() { 562 int group_id = -1; 563 int volume = 6; 564 boolean mute = false; 565 566 // Send a message to trigger volume state changed broadcast 567 VolumeControlStackEvent stackEvent = new VolumeControlStackEvent( 568 VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED); 569 stackEvent.device = mDevice; 570 stackEvent.valueInt1 = group_id; 571 stackEvent.valueInt2 = volume; 572 stackEvent.valueBool1 = mute; 573 mService.messageFromNative(stackEvent); 574 } 575 getLeAudioVolume(int index, int minIndex, int maxIndex, int streamType)576 int getLeAudioVolume(int index, int minIndex, int maxIndex, int streamType) { 577 // Note: This has to be the same as mBtHelper.setLeAudioVolume() 578 return (int) Math.round((double) index * BT_LE_AUDIO_MAX_VOL / maxIndex); 579 } 580 testVolumeCalculations(int streamType, int minIdx, int maxIdx)581 void testVolumeCalculations(int streamType, int minIdx, int maxIdx) { 582 // Send a message to trigger volume state changed broadcast 583 final VolumeControlStackEvent stackEvent = new VolumeControlStackEvent( 584 VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED); 585 stackEvent.device = null; 586 stackEvent.valueInt1 = 1; // groupId 587 stackEvent.valueBool1 = false; // isMuted 588 stackEvent.valueBool2 = true; // isAutonomous 589 590 IntStream.range(minIdx, maxIdx).forEach(idx -> { 591 // Given the reference volume index, set the LeAudio Volume 592 stackEvent.valueInt2 = getLeAudioVolume(idx, 593 mAudioManager.getStreamMinVolume(streamType), 594 mAudioManager.getStreamMaxVolume(streamType), streamType); 595 mService.messageFromNative(stackEvent); 596 597 // Verify that setting LeAudio Volume, sets the original volume index to Audio FW 598 verify(mAudioManager, times(1)).setStreamVolume(eq(streamType), eq(idx), anyInt()); 599 }); 600 } 601 602 @Test testAutonomousVolumeStateChange()603 public void testAutonomousVolumeStateChange() { 604 doReturn(AudioManager.MODE_IN_CALL).when(mAudioManager).getMode(); 605 testVolumeCalculations(AudioManager.STREAM_VOICE_CALL, CALL_MIN_VOL, CALL_MAX_VOL); 606 607 doReturn(AudioManager.MODE_NORMAL).when(mAudioManager).getMode(); 608 testVolumeCalculations(AudioManager.STREAM_MUSIC, MEDIA_MIN_VOL, MEDIA_MAX_VOL); 609 } 610 611 /** 612 * Test if autonomous Mute/Unmute propagates the event to audio manager. 613 */ 614 @Test testAutonomousMuteUnmute()615 public void testAutonomousMuteUnmute() { 616 int streamType = AudioManager.STREAM_MUSIC; 617 int streamVol = getLeAudioVolume(19, MEDIA_MIN_VOL, MEDIA_MAX_VOL, streamType); 618 619 // Send a message to trigger volume state changed broadcast 620 final VolumeControlStackEvent stackEvent = new VolumeControlStackEvent( 621 VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED); 622 stackEvent.device = null; 623 stackEvent.valueInt1 = 1; // groupId 624 stackEvent.valueInt2 = streamVol; 625 stackEvent.valueBool1 = false; // isMuted 626 stackEvent.valueBool2 = true; // isAutonomous 627 628 doReturn(false).when(mAudioManager) 629 .isStreamMute(eq(AudioManager.STREAM_MUSIC)); 630 631 // Verify that muting LeAudio device, sets the mute state on the audio device 632 stackEvent.valueBool1 = true; 633 mService.messageFromNative(stackEvent); 634 verify(mAudioManager, times(1)).adjustStreamVolume(eq(streamType), 635 eq(AudioManager.ADJUST_MUTE), anyInt()); 636 637 doReturn(true).when(mAudioManager) 638 .isStreamMute(eq(AudioManager.STREAM_MUSIC)); 639 640 // Verify that unmuting LeAudio device, unsets the mute state on the audio device 641 stackEvent.valueBool1 = false; 642 mService.messageFromNative(stackEvent); 643 verify(mAudioManager, times(1)).adjustStreamVolume(eq(streamType), 644 eq(AudioManager.ADJUST_UNMUTE), anyInt()); 645 } 646 647 /** 648 * Test Volume Control cache. 649 */ 650 @Test testVolumeCache()651 public void testVolumeCache() throws Exception { 652 int groupId = 1; 653 int volume = 6; 654 655 Assert.assertEquals(-1, mService.getGroupVolume(groupId)); 656 final SynchronousResultReceiver<Void> voidRecv = SynchronousResultReceiver.get(); 657 mServiceBinder.setGroupVolume(groupId, volume, mAttributionSource, voidRecv); 658 voidRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)); 659 660 final SynchronousResultReceiver<Integer> intRecv = SynchronousResultReceiver.get(); 661 int defaultRecvValue = -100; 662 mServiceBinder.getGroupVolume(groupId, mAttributionSource, intRecv); 663 int groupVolume = intRecv.awaitResultNoInterrupt( 664 Duration.ofMillis(TIMEOUT_MS)).getValue(defaultRecvValue); 665 Assert.assertEquals(volume, groupVolume); 666 667 volume = 10; 668 // Send autonomous volume change. 669 VolumeControlStackEvent stackEvent = new VolumeControlStackEvent( 670 VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED); 671 stackEvent.device = null; 672 stackEvent.valueInt1 = groupId; 673 stackEvent.valueInt2 = volume; 674 stackEvent.valueBool1 = false; 675 stackEvent.valueBool2 = true; /* autonomous */ 676 mService.messageFromNative(stackEvent); 677 678 Assert.assertEquals(volume, mService.getGroupVolume(groupId)); 679 } 680 681 /** 682 * Test Volume Control Mute cache. 683 */ 684 @Test testMuteCache()685 public void testMuteCache() throws Exception { 686 int groupId = 1; 687 int volume = 6; 688 689 Assert.assertEquals(false, mService.getGroupMute(groupId)); 690 691 // Send autonomous volume change 692 VolumeControlStackEvent stackEvent = new VolumeControlStackEvent( 693 VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED); 694 stackEvent.device = null; 695 stackEvent.valueInt1 = groupId; 696 stackEvent.valueInt2 = volume; 697 stackEvent.valueBool1 = false; /* unmuted */ 698 stackEvent.valueBool2 = true; /* autonomous */ 699 mService.messageFromNative(stackEvent); 700 701 // Mute 702 final SynchronousResultReceiver<Void> voidRecv = SynchronousResultReceiver.get(); 703 mServiceBinder.muteGroup(groupId, mAttributionSource, voidRecv); 704 voidRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)); 705 Assert.assertEquals(true, mService.getGroupMute(groupId)); 706 707 // Make sure the volume is kept even when muted 708 Assert.assertEquals(volume, mService.getGroupVolume(groupId)); 709 710 // Send autonomous unmute 711 stackEvent.valueBool1 = false; /* unmuted */ 712 mService.messageFromNative(stackEvent); 713 714 Assert.assertEquals(false, mService.getGroupMute(groupId)); 715 } 716 717 /** 718 * Test Volume Control with muted stream. 719 */ 720 @Test testVolumeChangeWhileMuted()721 public void testVolumeChangeWhileMuted() throws Exception { 722 int groupId = 1; 723 int volume = 6; 724 725 Assert.assertEquals(false, mService.getGroupMute(groupId)); 726 727 // Set the initial volume state 728 VolumeControlStackEvent stackEvent = new VolumeControlStackEvent( 729 VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED); 730 stackEvent.device = null; 731 stackEvent.valueInt1 = groupId; 732 stackEvent.valueInt2 = volume; 733 stackEvent.valueBool1 = false; /* unmuted */ 734 stackEvent.valueBool2 = true; /* autonomous */ 735 mService.messageFromNative(stackEvent); 736 737 // Mute 738 mService.muteGroup(groupId); 739 Assert.assertEquals(true, mService.getGroupMute(groupId)); 740 verify(mNativeInterface, times(1)).muteGroup(eq(groupId)); 741 742 // Make sure the volume is kept even when muted 743 doReturn(true).when(mAudioManager) 744 .isStreamMute(eq(AudioManager.STREAM_MUSIC)); 745 Assert.assertEquals(volume, mService.getGroupVolume(groupId)); 746 747 // Lower the volume and keep it mute 748 mService.setGroupVolume(groupId, --volume); 749 Assert.assertEquals(true, mService.getGroupMute(groupId)); 750 verify(mNativeInterface, times(1)).setGroupVolume(eq(groupId), eq(volume)); 751 verify(mNativeInterface, times(0)).unmuteGroup(eq(groupId)); 752 753 // Don't unmute on consecutive calls either 754 mService.setGroupVolume(groupId, --volume); 755 Assert.assertEquals(true, mService.getGroupMute(groupId)); 756 verify(mNativeInterface, times(1)).setGroupVolume(eq(groupId), eq(volume)); 757 verify(mNativeInterface, times(0)).unmuteGroup(eq(groupId)); 758 759 // Raise the volume and unmute 760 volume += 10; // avoid previous volume levels and simplify mock verification 761 doReturn(false).when(mAudioManager) 762 .isStreamMute(eq(AudioManager.STREAM_MUSIC)); 763 mService.setGroupVolume(groupId, ++volume); 764 Assert.assertEquals(false, mService.getGroupMute(groupId)); 765 verify(mNativeInterface, times(1)).setGroupVolume(eq(groupId), eq(volume)); 766 // Verify the number of unmute calls after the second volume change 767 mService.setGroupVolume(groupId, ++volume); 768 Assert.assertEquals(false, mService.getGroupMute(groupId)); 769 verify(mNativeInterface, times(1)).setGroupVolume(eq(groupId), eq(volume)); 770 // Make sure we unmuted only once 771 verify(mNativeInterface, times(1)).unmuteGroup(eq(groupId)); 772 } 773 774 /** 775 * Test setting volume for a group member who connects after the volume level 776 * for a group was already changed and cached. 777 */ 778 @Test testLateConnectingDevice()779 public void testLateConnectingDevice() throws Exception { 780 int groupId = 1; 781 int groupVolume = 56; 782 783 // Both devices are in the same group 784 when(mCsipService.getGroupId(mDevice, BluetoothUuid.CAP)).thenReturn(groupId); 785 when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(groupId); 786 787 // Update the device policy so okToConnect() returns true 788 when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); 789 when(mDatabaseManager 790 .getProfileConnectionPolicy(any(BluetoothDevice.class), 791 eq(BluetoothProfile.VOLUME_CONTROL))) 792 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); 793 doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class)); 794 doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class)); 795 796 generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTED, 797 BluetoothProfile.STATE_DISCONNECTED); 798 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 799 mService.getConnectionState(mDevice)); 800 Assert.assertTrue(mService.getDevices().contains(mDevice)); 801 802 mService.setGroupVolume(groupId, groupVolume); 803 verify(mNativeInterface, times(1)).setGroupVolume(eq(groupId), eq(groupVolume)); 804 verify(mNativeInterface, times(0)).setVolume(eq(mDeviceTwo), eq(groupVolume)); 805 806 // Verify that second device gets the proper group volume level when connected 807 generateConnectionMessageFromNative(mDeviceTwo, BluetoothProfile.STATE_CONNECTED, 808 BluetoothProfile.STATE_DISCONNECTED); 809 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 810 mService.getConnectionState(mDeviceTwo)); 811 Assert.assertTrue(mService.getDevices().contains(mDeviceTwo)); 812 verify(mNativeInterface, times(1)).setVolume(eq(mDeviceTwo), eq(groupVolume)); 813 } 814 815 /** 816 * Test setting volume for a new group member who is discovered after the volume level 817 * for a group was already changed and cached. 818 */ 819 @Test testLateDiscoveredGroupMember()820 public void testLateDiscoveredGroupMember() throws Exception { 821 int groupId = 1; 822 int groupVolume = 56; 823 824 // For now only one device is in the group 825 when(mCsipService.getGroupId(mDevice, BluetoothUuid.CAP)).thenReturn(groupId); 826 when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(-1); 827 828 // Update the device policy so okToConnect() returns true 829 when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); 830 when(mDatabaseManager 831 .getProfileConnectionPolicy(any(BluetoothDevice.class), 832 eq(BluetoothProfile.VOLUME_CONTROL))) 833 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); 834 doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class)); 835 doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class)); 836 837 generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTED, 838 BluetoothProfile.STATE_DISCONNECTED); 839 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 840 mService.getConnectionState(mDevice)); 841 Assert.assertTrue(mService.getDevices().contains(mDevice)); 842 843 // Set the group volume 844 mService.setGroupVolume(groupId, groupVolume); 845 846 // Verify that second device will not get the group volume level if it is not a group member 847 generateConnectionMessageFromNative(mDeviceTwo, BluetoothProfile.STATE_CONNECTED, 848 BluetoothProfile.STATE_DISCONNECTED); 849 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 850 mService.getConnectionState(mDeviceTwo)); 851 Assert.assertTrue(mService.getDevices().contains(mDeviceTwo)); 852 verify(mNativeInterface, times(0)).setVolume(eq(mDeviceTwo), eq(groupVolume)); 853 854 // But gets the volume when it becomes the group member 855 when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(groupId); 856 mService.handleGroupNodeAdded(groupId, mDeviceTwo); 857 verify(mNativeInterface, times(1)).setVolume(eq(mDeviceTwo), eq(groupVolume)); 858 } 859 860 /** 861 * Test setting volume to 0 for a group member who connects after the volume level 862 * for a group was already changed and cached. LeAudio has no knowledge of mute 863 * for anything else than telephony, thus setting volume level to 0 is considered 864 * as muting. 865 */ 866 @Test testMuteLateConnectingDevice()867 public void testMuteLateConnectingDevice() throws Exception { 868 int groupId = 1; 869 int volume = 100; 870 871 // Both devices are in the same group 872 when(mCsipService.getGroupId(mDevice, BluetoothUuid.CAP)).thenReturn(groupId); 873 when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(groupId); 874 875 // Update the device policy so okToConnect() returns true 876 when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); 877 when(mDatabaseManager 878 .getProfileConnectionPolicy(any(BluetoothDevice.class), 879 eq(BluetoothProfile.VOLUME_CONTROL))) 880 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); 881 doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class)); 882 doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class)); 883 884 generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTED, 885 BluetoothProfile.STATE_DISCONNECTED); 886 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 887 mService.getConnectionState(mDevice)); 888 Assert.assertTrue(mService.getDevices().contains(mDevice)); 889 890 // Set the initial volume and mute conditions 891 doReturn(true).when(mAudioManager).isStreamMute(anyInt()); 892 mService.setGroupVolume(groupId, volume); 893 894 verify(mNativeInterface, times(1)).setGroupVolume(eq(groupId), eq(volume)); 895 verify(mNativeInterface, times(0)).setVolume(eq(mDeviceTwo), eq(volume)); 896 // Check if it was muted 897 verify(mNativeInterface, times(1)).muteGroup(eq(groupId)); 898 899 Assert.assertEquals(true, mService.getGroupMute(groupId)); 900 901 // Verify that second device gets the proper group volume level when connected 902 generateConnectionMessageFromNative(mDeviceTwo, BluetoothProfile.STATE_CONNECTED, 903 BluetoothProfile.STATE_DISCONNECTED); 904 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 905 mService.getConnectionState(mDeviceTwo)); 906 Assert.assertTrue(mService.getDevices().contains(mDeviceTwo)); 907 verify(mNativeInterface, times(1)).setVolume(eq(mDeviceTwo), eq(volume)); 908 // Check if new device was muted 909 verify(mNativeInterface, times(1)).mute(eq(mDeviceTwo)); 910 } 911 912 /** 913 * Test setting volume to 0 for a new group member who is discovered after the volume level 914 * for a group was already changed and cached. LeAudio has no knowledge of mute 915 * for anything else than telephony, thus setting volume level to 0 is considered 916 * as muting. 917 */ 918 @Test testMuteLateDiscoveredGroupMember()919 public void testMuteLateDiscoveredGroupMember() throws Exception { 920 int groupId = 1; 921 int volume = 100; 922 923 // For now only one device is in the group 924 when(mCsipService.getGroupId(mDevice, BluetoothUuid.CAP)).thenReturn(groupId); 925 when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(-1); 926 927 // Update the device policy so okToConnect() returns true 928 when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); 929 when(mDatabaseManager 930 .getProfileConnectionPolicy(any(BluetoothDevice.class), 931 eq(BluetoothProfile.VOLUME_CONTROL))) 932 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); 933 doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class)); 934 doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class)); 935 936 generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTED, 937 BluetoothProfile.STATE_DISCONNECTED); 938 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 939 mService.getConnectionState(mDevice)); 940 Assert.assertTrue(mService.getDevices().contains(mDevice)); 941 942 // Set the initial volume and mute conditions 943 doReturn(true).when(mAudioManager).isStreamMute(anyInt()); 944 mService.setGroupVolume(groupId, volume); 945 946 // Verify that second device will not get the group volume level if it is not a group member 947 generateConnectionMessageFromNative(mDeviceTwo, BluetoothProfile.STATE_CONNECTED, 948 BluetoothProfile.STATE_DISCONNECTED); 949 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 950 mService.getConnectionState(mDeviceTwo)); 951 Assert.assertTrue(mService.getDevices().contains(mDeviceTwo)); 952 verify(mNativeInterface, times(0)).setVolume(eq(mDeviceTwo), eq(volume)); 953 // Check if it was not muted 954 verify(mNativeInterface, times(0)).mute(eq(mDeviceTwo)); 955 956 // But gets the volume when it becomes the group member 957 when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(groupId); 958 mService.handleGroupNodeAdded(groupId, mDeviceTwo); 959 verify(mNativeInterface, times(1)).setVolume(eq(mDeviceTwo), eq(volume)); 960 verify(mNativeInterface, times(1)).mute(eq(mDeviceTwo)); 961 } 962 963 @Test testServiceBinderGetDevicesMatchingConnectionStates()964 public void testServiceBinderGetDevicesMatchingConnectionStates() throws Exception { 965 final SynchronousResultReceiver<List<BluetoothDevice>> recv = 966 SynchronousResultReceiver.get(); 967 mServiceBinder.getDevicesMatchingConnectionStates(null, mAttributionSource, recv); 968 List<BluetoothDevice> devices = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)) 969 .getValue(null); 970 Assert.assertEquals(0, devices.size()); 971 } 972 973 @Test testServiceBinderSetConnectionPolicy()974 public void testServiceBinderSetConnectionPolicy() throws Exception { 975 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 976 boolean defaultRecvValue = false; 977 mServiceBinder.setConnectionPolicy( 978 mDevice, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, mAttributionSource, recv); 979 Assert.assertTrue(recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)) 980 .getValue(defaultRecvValue)); 981 verify(mDatabaseManager).setProfileConnectionPolicy( 982 mDevice, BluetoothProfile.VOLUME_CONTROL, BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 983 } 984 985 @Test testServiceBinderVolumeOffsetMethods()986 public void testServiceBinderVolumeOffsetMethods() throws Exception { 987 // Send a message to trigger connection completed 988 VolumeControlStackEvent event = new VolumeControlStackEvent( 989 VolumeControlStackEvent.EVENT_TYPE_DEVICE_AVAILABLE); 990 event.device = mDevice; 991 event.valueInt1 = 2; // number of external outputs 992 mService.messageFromNative(event); 993 994 final SynchronousResultReceiver<Boolean> boolRecv = SynchronousResultReceiver.get(); 995 boolean defaultRecvValue = false; 996 mServiceBinder.isVolumeOffsetAvailable(mDevice, mAttributionSource, boolRecv); 997 Assert.assertTrue(boolRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)) 998 .getValue(defaultRecvValue)); 999 1000 int volumeOffset = 100; 1001 final SynchronousResultReceiver<Void> voidRecv = SynchronousResultReceiver.get(); 1002 mServiceBinder.setVolumeOffset(mDevice, volumeOffset, mAttributionSource, voidRecv); 1003 voidRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)); 1004 verify(mNativeInterface).setExtAudioOutVolumeOffset(mDevice, 1, volumeOffset); 1005 } 1006 1007 @Test testServiceBinderRegisterUnregisterCallback()1008 public void testServiceBinderRegisterUnregisterCallback() throws Exception { 1009 IBluetoothVolumeControlCallback callback = 1010 Mockito.mock(IBluetoothVolumeControlCallback.class); 1011 Binder binder = Mockito.mock(Binder.class); 1012 when(callback.asBinder()).thenReturn(binder); 1013 1014 int size = mService.mCallbacks.getRegisteredCallbackCount(); 1015 SynchronousResultReceiver<Void> recv = SynchronousResultReceiver.get(); 1016 mServiceBinder.registerCallback(callback, mAttributionSource, recv); 1017 recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)).getValue(null); 1018 Assert.assertEquals(size + 1, mService.mCallbacks.getRegisteredCallbackCount()); 1019 1020 recv = SynchronousResultReceiver.get(); 1021 mServiceBinder.unregisterCallback(callback, mAttributionSource, recv); 1022 recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)).getValue(null); 1023 Assert.assertEquals(size, mService.mCallbacks.getRegisteredCallbackCount()); 1024 } 1025 1026 @Test testServiceBinderMuteMethods()1027 public void testServiceBinderMuteMethods() throws Exception { 1028 SynchronousResultReceiver<Void> voidRecv = SynchronousResultReceiver.get(); 1029 mServiceBinder.mute(mDevice, mAttributionSource, voidRecv); 1030 voidRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)); 1031 verify(mNativeInterface).mute(mDevice); 1032 1033 voidRecv = SynchronousResultReceiver.get(); 1034 mServiceBinder.unmute(mDevice, mAttributionSource, voidRecv); 1035 voidRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)); 1036 verify(mNativeInterface).unmute(mDevice); 1037 1038 int groupId = 1; 1039 voidRecv = SynchronousResultReceiver.get(); 1040 mServiceBinder.muteGroup(groupId, mAttributionSource, voidRecv); 1041 voidRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)); 1042 verify(mNativeInterface).muteGroup(groupId); 1043 1044 voidRecv = SynchronousResultReceiver.get(); 1045 mServiceBinder.unmuteGroup(groupId, mAttributionSource, voidRecv); 1046 voidRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)); 1047 verify(mNativeInterface).unmuteGroup(groupId); 1048 } 1049 1050 @Test testVolumeControlOffsetDescriptor()1051 public void testVolumeControlOffsetDescriptor() { 1052 VolumeControlService.VolumeControlOffsetDescriptor descriptor = 1053 new VolumeControlService.VolumeControlOffsetDescriptor(); 1054 int invalidId = -1; 1055 int validId = 10; 1056 int testValue = 100; 1057 String testDesc = "testDescription"; 1058 int testLocation = 10000; 1059 1060 Assert.assertEquals(0, descriptor.size()); 1061 descriptor.add(validId); 1062 Assert.assertEquals(1, descriptor.size()); 1063 1064 Assert.assertFalse(descriptor.setValue(invalidId, testValue)); 1065 Assert.assertTrue(descriptor.setValue(validId, testValue)); 1066 Assert.assertEquals(0, descriptor.getValue(invalidId)); 1067 Assert.assertEquals(testValue, descriptor.getValue(validId)); 1068 1069 Assert.assertFalse(descriptor.setDescription(invalidId, testDesc)); 1070 Assert.assertTrue(descriptor.setDescription(validId, testDesc)); 1071 Assert.assertEquals(null, descriptor.getDescription(invalidId)); 1072 Assert.assertEquals(testDesc, descriptor.getDescription(validId)); 1073 1074 Assert.assertFalse(descriptor.setLocation(invalidId, testLocation)); 1075 Assert.assertTrue(descriptor.setLocation(validId, testLocation)); 1076 Assert.assertEquals(0, descriptor.getLocation(invalidId)); 1077 Assert.assertEquals(testLocation, descriptor.getLocation(validId)); 1078 1079 StringBuilder sb = new StringBuilder(); 1080 descriptor.dump(sb); 1081 Assert.assertTrue(sb.toString().contains(testDesc)); 1082 1083 descriptor.add(validId + 1); 1084 Assert.assertEquals(2, descriptor.size()); 1085 descriptor.remove(validId); 1086 Assert.assertEquals(1, descriptor.size()); 1087 descriptor.clear(); 1088 Assert.assertEquals(0, descriptor.size()); 1089 } 1090 1091 @Test testDump_doesNotCrash()1092 public void testDump_doesNotCrash() throws Exception { 1093 connectDevice(mDevice); 1094 1095 StringBuilder sb = new StringBuilder(); 1096 mService.dump(sb); 1097 } 1098 connectDevice(BluetoothDevice device)1099 private void connectDevice(BluetoothDevice device) throws Exception { 1100 VolumeControlStackEvent connCompletedEvent; 1101 1102 List<BluetoothDevice> prevConnectedDevices = mService.getConnectedDevices(); 1103 1104 // Update the device policy so okToConnect() returns true 1105 when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); 1106 when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.VOLUME_CONTROL)) 1107 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); 1108 doReturn(true).when(mNativeInterface).connectVolumeControl(device); 1109 doReturn(true).when(mNativeInterface).disconnectVolumeControl(device); 1110 1111 // Send a connect request 1112 Assert.assertTrue("Connect failed", mService.connect(device)); 1113 1114 // Verify the connection state broadcast, and that we are in Connecting state 1115 verifyConnectionStateIntent(TIMEOUT_MS, device, BluetoothProfile.STATE_CONNECTING, 1116 BluetoothProfile.STATE_DISCONNECTED); 1117 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 1118 mService.getConnectionState(device)); 1119 1120 // Send a message to trigger connection completed 1121 connCompletedEvent = new VolumeControlStackEvent( 1122 VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 1123 connCompletedEvent.device = device; 1124 connCompletedEvent.valueInt1 = VolumeControlStackEvent.CONNECTION_STATE_CONNECTED; 1125 mService.messageFromNative(connCompletedEvent); 1126 1127 // Verify the connection state broadcast, and that we are in Connected state 1128 verifyConnectionStateIntent(TIMEOUT_MS, device, BluetoothProfile.STATE_CONNECTED, 1129 BluetoothProfile.STATE_CONNECTING); 1130 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 1131 mService.getConnectionState(device)); 1132 1133 // Verify that the device is in the list of connected devices 1134 final SynchronousResultReceiver<List<BluetoothDevice>> recv = 1135 SynchronousResultReceiver.get(); 1136 mServiceBinder.getConnectedDevices(mAttributionSource, recv); 1137 List<BluetoothDevice> connectedDevices = 1138 recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)).getValue(null); 1139 Assert.assertTrue(connectedDevices.contains(device)); 1140 // Verify the list of previously connected devices 1141 for (BluetoothDevice prevDevice : prevConnectedDevices) { 1142 Assert.assertTrue(connectedDevices.contains(prevDevice)); 1143 } 1144 } 1145 generateConnectionMessageFromNative(BluetoothDevice device, int newConnectionState, int oldConnectionState)1146 private void generateConnectionMessageFromNative(BluetoothDevice device, int newConnectionState, 1147 int oldConnectionState) { 1148 VolumeControlStackEvent stackEvent = 1149 new VolumeControlStackEvent( 1150 VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 1151 stackEvent.device = device; 1152 stackEvent.valueInt1 = newConnectionState; 1153 mService.messageFromNative(stackEvent); 1154 // Verify the connection state broadcast 1155 verifyConnectionStateIntent(TIMEOUT_MS, device, newConnectionState, oldConnectionState); 1156 } 1157 generateUnexpectedConnectionMessageFromNative(BluetoothDevice device, int newConnectionState, int oldConnectionState)1158 private void generateUnexpectedConnectionMessageFromNative(BluetoothDevice device, 1159 int newConnectionState, int oldConnectionState) { 1160 VolumeControlStackEvent stackEvent = 1161 new VolumeControlStackEvent( 1162 VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 1163 stackEvent.device = device; 1164 stackEvent.valueInt1 = newConnectionState; 1165 mService.messageFromNative(stackEvent); 1166 // Verify the connection state broadcast 1167 verifyNoConnectionStateIntent(TIMEOUT_MS, device); 1168 } 1169 1170 /** 1171 * Helper function to test okToConnect() method 1172 * 1173 * @param device test device 1174 * @param bondState bond state value, could be invalid 1175 * @param policy value, could be invalid 1176 * @param expected expected result from okToConnect() 1177 */ testOkToConnectCase(BluetoothDevice device, int bondState, int policy, boolean expected)1178 private void testOkToConnectCase(BluetoothDevice device, int bondState, int policy, 1179 boolean expected) { 1180 doReturn(bondState).when(mAdapterService).getBondState(device); 1181 when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); 1182 when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.VOLUME_CONTROL)) 1183 .thenReturn(policy); 1184 Assert.assertEquals(expected, mService.okToConnect(device)); 1185 } 1186 } 1187