• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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