1 package com.android.bluetooth.hfpclient; 2 3 import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.AT_OK; 4 import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.VOICE_RECOGNITION_START; 5 import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.VOICE_RECOGNITION_STOP; 6 7 import static org.mockito.Mockito.*; 8 9 import android.bluetooth.BluetoothAdapter; 10 import android.bluetooth.BluetoothAssignedNumbers; 11 import android.bluetooth.BluetoothDevice; 12 import android.bluetooth.BluetoothHeadsetClient; 13 import android.bluetooth.BluetoothHeadsetClientCall; 14 import android.bluetooth.BluetoothProfile; 15 import android.content.Context; 16 import android.content.Intent; 17 import android.content.res.Resources; 18 import android.media.AudioManager; 19 import android.os.Bundle; 20 import android.os.HandlerThread; 21 import android.os.Message; 22 23 import androidx.test.InstrumentationRegistry; 24 import androidx.test.espresso.intent.matcher.IntentMatchers; 25 import androidx.test.filters.FlakyTest; 26 import androidx.test.filters.LargeTest; 27 import androidx.test.filters.MediumTest; 28 import androidx.test.filters.SmallTest; 29 import androidx.test.runner.AndroidJUnit4; 30 31 import com.android.bluetooth.R; 32 import com.android.bluetooth.TestUtils; 33 import com.android.bluetooth.Utils; 34 35 import org.hamcrest.core.AllOf; 36 import org.hamcrest.core.IsInstanceOf; 37 import org.junit.After; 38 import org.junit.Assert; 39 import org.junit.Assume; 40 import org.junit.Before; 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 import org.mockito.ArgumentCaptor; 44 import org.mockito.Mock; 45 import org.mockito.MockitoAnnotations; 46 import org.mockito.hamcrest.MockitoHamcrest; 47 48 @LargeTest 49 @RunWith(AndroidJUnit4.class) 50 public class HeadsetClientStateMachineTest { 51 private BluetoothAdapter mAdapter; 52 private HandlerThread mHandlerThread; 53 private HeadsetClientStateMachine mHeadsetClientStateMachine; 54 private BluetoothDevice mTestDevice; 55 private Context mTargetContext; 56 57 @Mock 58 private Resources mMockHfpResources; 59 @Mock 60 private HeadsetClientService mHeadsetClientService; 61 @Mock 62 private AudioManager mAudioManager; 63 64 private NativeInterface mNativeInterface; 65 66 private static final int STANDARD_WAIT_MILLIS = 1000; 67 private static final int QUERY_CURRENT_CALLS_WAIT_MILLIS = 2000; 68 private static final int QUERY_CURRENT_CALLS_TEST_WAIT_MILLIS = QUERY_CURRENT_CALLS_WAIT_MILLIS 69 * 3 / 2; 70 71 @Before setUp()72 public void setUp() { 73 mTargetContext = InstrumentationRegistry.getTargetContext(); 74 Assume.assumeTrue("Ignore test when HeadsetClientService is not enabled", 75 mTargetContext.getResources().getBoolean(R.bool.profile_supported_hfpclient)); 76 // Setup mocks and test assets 77 MockitoAnnotations.initMocks(this); 78 // Set a valid volume 79 when(mAudioManager.getStreamVolume(anyInt())).thenReturn(2); 80 when(mAudioManager.getStreamMaxVolume(anyInt())).thenReturn(10); 81 when(mAudioManager.getStreamMinVolume(anyInt())).thenReturn(1); 82 when(mHeadsetClientService.getAudioManager()).thenReturn( 83 mAudioManager); 84 when(mHeadsetClientService.getResources()).thenReturn(mMockHfpResources); 85 when(mMockHfpResources.getBoolean(R.bool.hfp_clcc_poll_during_call)).thenReturn(true); 86 mNativeInterface = spy(NativeInterface.getInstance()); 87 88 // This line must be called to make sure relevant objects are initialized properly 89 mAdapter = BluetoothAdapter.getDefaultAdapter(); 90 // Get a device for testing 91 mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05"); 92 93 // Setup thread and looper 94 mHandlerThread = new HandlerThread("HeadsetClientStateMachineTestHandlerThread"); 95 mHandlerThread.start(); 96 // Manage looper execution in main test thread explicitly to guarantee timing consistency 97 mHeadsetClientStateMachine = 98 new HeadsetClientStateMachine(mHeadsetClientService, mHandlerThread.getLooper(), 99 mNativeInterface); 100 mHeadsetClientStateMachine.start(); 101 TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); 102 } 103 104 @After tearDown()105 public void tearDown() { 106 if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hfpclient)) { 107 return; 108 } 109 TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); 110 mHeadsetClientStateMachine.doQuit(); 111 mHandlerThread.quit(); 112 } 113 114 /** 115 * Test that default state is disconnected 116 */ 117 @SmallTest 118 @Test testDefaultDisconnectedState()119 public void testDefaultDisconnectedState() { 120 Assert.assertEquals(mHeadsetClientStateMachine.getConnectionState(null), 121 BluetoothProfile.STATE_DISCONNECTED); 122 } 123 124 /** 125 * Test that an incoming connection with low priority is rejected 126 */ 127 @MediumTest 128 @Test testIncomingPriorityReject()129 public void testIncomingPriorityReject() { 130 // Return false for priority. 131 when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn( 132 BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); 133 134 // Inject an event for when incoming connection is requested 135 StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 136 connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED; 137 connStCh.device = mTestDevice; 138 mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, connStCh); 139 140 // Verify that only DISCONNECTED -> DISCONNECTED broadcast is fired 141 verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS)).sendBroadcast(MockitoHamcrest 142 .argThat( 143 AllOf.allOf(IntentMatchers.hasAction( 144 BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED), 145 IntentMatchers.hasExtra(BluetoothProfile.EXTRA_STATE, 146 BluetoothProfile.STATE_DISCONNECTED), 147 IntentMatchers.hasExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 148 BluetoothProfile.STATE_DISCONNECTED))), anyString(), 149 any(Bundle.class)); 150 // Check we are in disconnected state still. 151 Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(), 152 IsInstanceOf.instanceOf(HeadsetClientStateMachine.Disconnected.class)); 153 } 154 155 /** 156 * Test that an incoming connection with high priority is accepted 157 */ 158 @MediumTest 159 @Test testIncomingPriorityAccept()160 public void testIncomingPriorityAccept() { 161 // Return true for priority. 162 when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn( 163 BluetoothProfile.CONNECTION_POLICY_ALLOWED); 164 165 // Inject an event for when incoming connection is requested 166 StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 167 connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED; 168 connStCh.device = mTestDevice; 169 mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, connStCh); 170 171 // Verify that one connection state broadcast is executed 172 ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class); 173 verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS)).sendBroadcast(intentArgument1 174 .capture(), 175 anyString(), any(Bundle.class)); 176 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 177 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); 178 179 // Check we are in connecting state now. 180 Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(), 181 IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connecting.class)); 182 183 // Send a message to trigger SLC connection 184 StackEvent slcEvent = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 185 slcEvent.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED; 186 slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS; 187 slcEvent.device = mTestDevice; 188 mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, slcEvent); 189 190 // Verify that one connection state broadcast is executed 191 ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class); 192 verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(2)).sendBroadcast( 193 intentArgument2.capture(), anyString(), any(Bundle.class)); 194 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 195 intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); 196 // Check we are in connecting state now. 197 Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(), 198 IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class)); 199 } 200 201 /** 202 * Test that an incoming connection that times out 203 */ 204 @MediumTest 205 @Test testIncomingTimeout()206 public void testIncomingTimeout() { 207 // Return true for priority. 208 when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn( 209 BluetoothProfile.CONNECTION_POLICY_ALLOWED); 210 211 // Inject an event for when incoming connection is requested 212 StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 213 connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED; 214 connStCh.device = mTestDevice; 215 mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, connStCh); 216 217 // Verify that one connection state broadcast is executed 218 ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class); 219 verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS)).sendBroadcast(intentArgument1 220 .capture(), 221 anyString(), any(Bundle.class)); 222 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 223 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); 224 225 // Check we are in connecting state now. 226 Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(), 227 IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connecting.class)); 228 229 // Verify that one connection state broadcast is executed 230 ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class); 231 verify(mHeadsetClientService, 232 timeout(HeadsetClientStateMachine.CONNECTING_TIMEOUT_MS * 2).times( 233 2)).sendBroadcast(intentArgument2.capture(), anyString(), 234 any(Bundle.class)); 235 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, 236 intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); 237 238 // Check we are in connecting state now. 239 Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(), 240 IsInstanceOf.instanceOf(HeadsetClientStateMachine.Disconnected.class)); 241 } 242 243 /** 244 * Test that In Band Ringtone information is relayed from phone. 245 */ 246 @LargeTest 247 @Test 248 @FlakyTest testInBandRingtone()249 public void testInBandRingtone() { 250 // Return true for priority. 251 when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn( 252 BluetoothProfile.CONNECTION_POLICY_ALLOWED); 253 254 Assert.assertEquals(false, mHeadsetClientStateMachine.getInBandRing()); 255 256 // Inject an event for when incoming connection is requested 257 StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 258 connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED; 259 connStCh.device = mTestDevice; 260 mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, connStCh); 261 262 // Verify that one connection state broadcast is executed 263 ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class); 264 verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS)).sendBroadcast(intentArgument 265 .capture(), 266 anyString(), any(Bundle.class)); 267 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 268 intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); 269 270 // Send a message to trigger SLC connection 271 StackEvent slcEvent = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 272 slcEvent.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED; 273 slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS; 274 slcEvent.device = mTestDevice; 275 mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, slcEvent); 276 277 verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(2)).sendBroadcast( 278 intentArgument.capture(), 279 anyString(), any(Bundle.class)); 280 281 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 282 intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); 283 284 StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_IN_BAND_RINGTONE); 285 event.valueInt = 0; 286 event.device = mTestDevice; 287 288 // Enable In Band Ring and verify state gets propagated. 289 StackEvent eventInBandRing = new StackEvent(StackEvent.EVENT_TYPE_IN_BAND_RINGTONE); 290 eventInBandRing.valueInt = 1; 291 eventInBandRing.device = mTestDevice; 292 mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventInBandRing); 293 verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(3)).sendBroadcast( 294 intentArgument.capture(), 295 anyString(), any(Bundle.class)); 296 Assert.assertEquals(1, 297 intentArgument.getValue().getIntExtra(BluetoothHeadsetClient.EXTRA_IN_BAND_RING, 298 -1)); 299 Assert.assertEquals(true, mHeadsetClientStateMachine.getInBandRing()); 300 301 // Simulate a new incoming phone call 302 StackEvent eventCallStatusUpdated = new StackEvent(StackEvent.EVENT_TYPE_CLIP); 303 TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); 304 mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventCallStatusUpdated); 305 TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); 306 verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(3)).sendBroadcast( 307 intentArgument.capture(), 308 anyString(),any(Bundle.class)); 309 310 // Provide information about the new call 311 StackEvent eventIncomingCall = new StackEvent(StackEvent.EVENT_TYPE_CURRENT_CALLS); 312 eventIncomingCall.valueInt = 1; //index 313 eventIncomingCall.valueInt2 = 1; //direction 314 eventIncomingCall.valueInt3 = 4; //state 315 eventIncomingCall.valueInt4 = 0; //multi party 316 eventIncomingCall.valueString = "5551212"; //phone number 317 eventIncomingCall.device = mTestDevice; 318 319 mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventIncomingCall); 320 verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(3)).sendBroadcast( 321 intentArgument.capture(), 322 anyString(), any(Bundle.class)); 323 324 325 // Signal that the complete list of calls was received. 326 StackEvent eventCommandStatus = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT); 327 eventCommandStatus.valueInt = AT_OK; 328 mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventCommandStatus); 329 TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); 330 verify(mHeadsetClientService, timeout(QUERY_CURRENT_CALLS_TEST_WAIT_MILLIS).times(4)) 331 .sendBroadcast( 332 intentArgument.capture(), 333 anyString(), any(Bundle.class)); 334 // Verify that the new call is being registered with the inBandRing flag set. 335 Assert.assertEquals(true, 336 ((BluetoothHeadsetClientCall) intentArgument.getValue().getParcelableExtra( 337 BluetoothHeadsetClient.EXTRA_CALL)).isInBandRing()); 338 339 // Disable In Band Ring and verify state gets propagated. 340 eventInBandRing.valueInt = 0; 341 mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventInBandRing); 342 verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(5)).sendBroadcast( 343 intentArgument.capture(), 344 anyString(), any(Bundle.class)); 345 Assert.assertEquals(0, 346 intentArgument.getValue().getIntExtra(BluetoothHeadsetClient.EXTRA_IN_BAND_RING, 347 -1)); 348 Assert.assertEquals(false, mHeadsetClientStateMachine.getInBandRing()); 349 350 } 351 352 /* Utility function to simulate HfpClient is connected. */ setUpHfpClientConnection(int startBroadcastIndex)353 private int setUpHfpClientConnection(int startBroadcastIndex) { 354 // Trigger an incoming connection is requested 355 StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 356 connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED; 357 connStCh.device = mTestDevice; 358 mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, connStCh); 359 ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class); 360 verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(startBroadcastIndex)) 361 .sendBroadcast(intentArgument.capture(), anyString(), any(Bundle.class)); 362 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, 363 intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); 364 startBroadcastIndex++; 365 return startBroadcastIndex; 366 } 367 368 /* Utility function to simulate SLC connection. */ setUpServiceLevelConnection(int startBroadcastIndex)369 private int setUpServiceLevelConnection(int startBroadcastIndex) { 370 // Trigger SLC connection 371 StackEvent slcEvent = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 372 slcEvent.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED; 373 slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS; 374 slcEvent.valueInt2 |= HeadsetClientHalConstants.PEER_FEAT_HF_IND; 375 slcEvent.device = mTestDevice; 376 mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, slcEvent); 377 ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class); 378 verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(startBroadcastIndex)) 379 .sendBroadcast(intentArgument.capture(), anyString(), any(Bundle.class)); 380 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, 381 intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); 382 startBroadcastIndex++; 383 return startBroadcastIndex; 384 } 385 386 /* Utility function: supported AT command should lead to native call */ runSupportedVendorAtCommand(String atCommand, int vendorId)387 private void runSupportedVendorAtCommand(String atCommand, int vendorId) { 388 // Return true for priority. 389 when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn( 390 BluetoothProfile.CONNECTION_POLICY_ALLOWED); 391 392 int expectedBroadcastIndex = 1; 393 394 expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex); 395 expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex); 396 397 Message msg = mHeadsetClientStateMachine.obtainMessage( 398 HeadsetClientStateMachine.SEND_VENDOR_AT_COMMAND, vendorId, 0, atCommand); 399 mHeadsetClientStateMachine.sendMessage(msg); 400 401 verify(mNativeInterface, timeout(STANDARD_WAIT_MILLIS).times(1)).sendATCmd( 402 Utils.getBytesFromAddress(mTestDevice.getAddress()), 403 HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_VENDOR_SPECIFIC_CMD, 404 0, 0, atCommand); 405 } 406 407 /** 408 * Test: supported vendor specific command: set operation 409 */ 410 @LargeTest 411 @Test testSupportedVendorAtCommandSet()412 public void testSupportedVendorAtCommandSet() { 413 int vendorId = BluetoothAssignedNumbers.APPLE; 414 String atCommand = "+XAPL=ABCD-1234-0100,100"; 415 runSupportedVendorAtCommand(atCommand, vendorId); 416 } 417 418 /** 419 * Test: supported vendor specific command: read operation 420 */ 421 @LargeTest 422 @Test testSupportedVendorAtCommandRead()423 public void testSupportedVendorAtCommandRead() { 424 int vendorId = BluetoothAssignedNumbers.APPLE; 425 String atCommand = "+APLSIRI?"; 426 runSupportedVendorAtCommand(atCommand, vendorId); 427 } 428 429 /* utility function: unsupported vendor specific command shall be filtered. */ runUnsupportedVendorAtCommand(String atCommand, int vendorId)430 public void runUnsupportedVendorAtCommand(String atCommand, int vendorId) { 431 // Return true for priority. 432 when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn( 433 BluetoothProfile.CONNECTION_POLICY_ALLOWED); 434 435 int expectedBroadcastIndex = 1; 436 437 expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex); 438 expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex); 439 440 Message msg = mHeadsetClientStateMachine.obtainMessage( 441 HeadsetClientStateMachine.SEND_VENDOR_AT_COMMAND, vendorId, 0, atCommand); 442 mHeadsetClientStateMachine.sendMessage(msg); 443 444 verify(mNativeInterface, timeout(STANDARD_WAIT_MILLIS).times(0)) 445 .sendATCmd(any(), anyInt(), anyInt(), anyInt(), any()); 446 } 447 448 /** 449 * Test: unsupported vendor specific command shall be filtered: bad command code 450 */ 451 @LargeTest 452 @Test testUnsupportedVendorAtCommandBadCode()453 public void testUnsupportedVendorAtCommandBadCode() { 454 String atCommand = "+XAAPL=ABCD-1234-0100,100"; 455 int vendorId = BluetoothAssignedNumbers.APPLE; 456 runUnsupportedVendorAtCommand(atCommand, vendorId); 457 } 458 459 /** 460 * Test: unsupported vendor specific command shall be filtered: 461 * no back to back command 462 */ 463 @LargeTest 464 @Test testUnsupportedVendorAtCommandBackToBack()465 public void testUnsupportedVendorAtCommandBackToBack() { 466 String atCommand = "+XAPL=ABCD-1234-0100,100; +XAPL=ab"; 467 int vendorId = BluetoothAssignedNumbers.APPLE; 468 runUnsupportedVendorAtCommand(atCommand, vendorId); 469 } 470 471 /* Utility test function: supported vendor specific event 472 * shall lead to broadcast intent 473 */ runSupportedVendorEvent(int vendorId, String vendorEventCode, String vendorEventArgument)474 private void runSupportedVendorEvent(int vendorId, String vendorEventCode, 475 String vendorEventArgument) { 476 // Setup connection state machine to be in connected state 477 when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn( 478 BluetoothProfile.CONNECTION_POLICY_ALLOWED); 479 int expectedBroadcastIndex = 1; 480 expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex); 481 expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex); 482 483 // Simulate a known event arrive 484 String vendorEvent = vendorEventCode + vendorEventArgument; 485 StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT); 486 event.device = mTestDevice; 487 event.valueString = vendorEvent; 488 mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, event); 489 490 // Validate broadcast intent 491 ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class); 492 verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(expectedBroadcastIndex)) 493 .sendBroadcast(intentArgument.capture(), anyString(), any(Bundle.class)); 494 Assert.assertEquals(BluetoothHeadsetClient.ACTION_VENDOR_SPECIFIC_HEADSETCLIENT_EVENT, 495 intentArgument.getValue().getAction()); 496 Assert.assertEquals(vendorId, 497 intentArgument.getValue().getIntExtra(BluetoothHeadsetClient.EXTRA_VENDOR_ID, -1)); 498 Assert.assertEquals(vendorEventCode, 499 intentArgument.getValue().getStringExtra( 500 BluetoothHeadsetClient.EXTRA_VENDOR_EVENT_CODE)); 501 Assert.assertEquals(vendorEvent, 502 intentArgument.getValue().getStringExtra( 503 BluetoothHeadsetClient.EXTRA_VENDOR_EVENT_FULL_ARGS)); 504 } 505 506 /** 507 * Test: supported vendor specific response: response to read command 508 */ 509 @LargeTest 510 @Test testSupportedVendorEventReadResponse()511 public void testSupportedVendorEventReadResponse() { 512 final int vendorId = BluetoothAssignedNumbers.APPLE; 513 final String vendorResponseCode = "+XAPL="; 514 final String vendorResponseArgument = "iPhone,2"; 515 runSupportedVendorEvent(vendorId, vendorResponseCode, vendorResponseArgument); 516 } 517 518 /** 519 * Test: supported vendor specific response: response to test command 520 */ 521 @LargeTest 522 @Test testSupportedVendorEventTestResponse()523 public void testSupportedVendorEventTestResponse() { 524 final int vendorId = BluetoothAssignedNumbers.APPLE; 525 final String vendorResponseCode = "+APLSIRI:"; 526 final String vendorResponseArgumentWithSpace = " 2"; 527 runSupportedVendorEvent(vendorId, vendorResponseCode, vendorResponseArgumentWithSpace); 528 } 529 530 /* Utility test function: unsupported vendor specific response shall be filtered out*/ runUnsupportedVendorEvent(int vendorId, String vendorEventCode, String vendorEventArgument)531 public void runUnsupportedVendorEvent(int vendorId, String vendorEventCode, 532 String vendorEventArgument) { 533 // Setup connection state machine to be in connected state 534 when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn( 535 BluetoothProfile.CONNECTION_POLICY_ALLOWED); 536 int expectedBroadcastIndex = 1; 537 expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex); 538 expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex); 539 540 // Simulate an unknown event arrive 541 String vendorEvent = vendorEventCode + vendorEventArgument; 542 StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT); 543 event.device = mTestDevice; 544 event.valueString = vendorEvent; 545 mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, event); 546 547 // Validate no broadcast intent 548 verify(mHeadsetClientService, atMost(expectedBroadcastIndex - 1)) 549 .sendBroadcast(any(), anyString(), any(Bundle.class)); 550 } 551 552 /** 553 * Test unsupported vendor response: bad read response 554 */ 555 @LargeTest 556 @Test testUnsupportedVendorEventBadReadResponse()557 public void testUnsupportedVendorEventBadReadResponse() { 558 final int vendorId = BluetoothAssignedNumbers.APPLE; 559 final String vendorResponseCode = "+XAAPL="; 560 final String vendorResponseArgument = "iPhone,2"; 561 runUnsupportedVendorEvent(vendorId, vendorResponseCode, vendorResponseArgument); 562 } 563 564 /** 565 * Test unsupported vendor response: bad test response 566 */ 567 @LargeTest 568 @Test testUnsupportedVendorEventBadTestResponse()569 public void testUnsupportedVendorEventBadTestResponse() { 570 final int vendorId = BluetoothAssignedNumbers.APPLE; 571 final String vendorResponseCode = "+AAPLSIRI:"; 572 final String vendorResponseArgument = "2"; 573 runUnsupportedVendorEvent(vendorId, vendorResponseCode, vendorResponseArgument); 574 } 575 576 /** 577 * Test voice recognition state change broadcast. 578 */ 579 @MediumTest 580 @Test testVoiceRecognitionStateChange()581 public void testVoiceRecognitionStateChange() { 582 // Setup connection state machine to be in connected state 583 when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn( 584 BluetoothProfile.CONNECTION_POLICY_ALLOWED); 585 when(mNativeInterface.startVoiceRecognition(any(byte[].class))).thenReturn(true); 586 when(mNativeInterface.stopVoiceRecognition(any(byte[].class))).thenReturn(true); 587 588 int expectedBroadcastIndex = 1; 589 expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex); 590 expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex); 591 592 // Simulate a voice recognition start 593 mHeadsetClientStateMachine.sendMessage(VOICE_RECOGNITION_START); 594 595 // Signal that the complete list of actions was received. 596 StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT); 597 event.device = mTestDevice; 598 event.valueInt = AT_OK; 599 mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, event); 600 601 expectedBroadcastIndex = verifyVoiceRecognitionBroadcast(expectedBroadcastIndex, 602 HeadsetClientHalConstants.VR_STATE_STARTED); 603 604 // Simulate a voice recognition stop 605 mHeadsetClientStateMachine.sendMessage(VOICE_RECOGNITION_STOP); 606 607 // Signal that the complete list of actions was received. 608 event = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT); 609 event.device = mTestDevice; 610 event.valueInt = AT_OK; 611 mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, event); 612 613 verifyVoiceRecognitionBroadcast(expectedBroadcastIndex, 614 HeadsetClientHalConstants.VR_STATE_STOPPED); 615 } 616 verifyVoiceRecognitionBroadcast(int expectedBroadcastIndex, int expectedState)617 private int verifyVoiceRecognitionBroadcast(int expectedBroadcastIndex, int expectedState) { 618 // Validate broadcast intent 619 ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class); 620 verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(expectedBroadcastIndex)) 621 .sendBroadcast(intentArgument.capture(), anyString(), any(Bundle.class)); 622 Assert.assertEquals(BluetoothHeadsetClient.ACTION_AG_EVENT, 623 intentArgument.getValue().getAction()); 624 int state = intentArgument.getValue().getIntExtra( 625 BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION, -1); 626 Assert.assertEquals(expectedState, state); 627 return expectedBroadcastIndex + 1; 628 } 629 630 /** 631 * Test send BIEV command 632 */ 633 @MediumTest 634 @Test testSendBIEVCommand()635 public void testSendBIEVCommand() { 636 // Setup connection state machine to be in connected state 637 when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn( 638 BluetoothProfile.CONNECTION_POLICY_ALLOWED); 639 int expectedBroadcastIndex = 1; 640 expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex); 641 expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex); 642 643 int indicator_id = 2; 644 int indicator_value = 50; 645 646 Message msg = mHeadsetClientStateMachine.obtainMessage(HeadsetClientStateMachine.SEND_BIEV); 647 msg.arg1 = indicator_id; 648 msg.arg2 = indicator_value; 649 650 mHeadsetClientStateMachine.sendMessage(msg); 651 652 verify(mNativeInterface, timeout(STANDARD_WAIT_MILLIS).times(1)) 653 .sendATCmd( 654 Utils.getBytesFromAddress(mTestDevice.getAddress()), 655 HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_BIEV, 656 indicator_id, 657 indicator_value, 658 null); 659 } 660 661 /** 662 * Test state machine shall try to send AT+BIEV command to AG 663 * to update an init battery level. 664 */ 665 @MediumTest 666 @Test testSendBatteryUpdateIndicatorWhenConnect()667 public void testSendBatteryUpdateIndicatorWhenConnect() { 668 // Setup connection state machine to be in connected state 669 when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn( 670 BluetoothProfile.CONNECTION_POLICY_ALLOWED); 671 int expectedBroadcastIndex = 1; 672 673 expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex); 674 expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex); 675 676 verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(1)) 677 .updateBatteryLevel(); 678 } 679 } 680