• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.bluetooth.hfpclient;
18 
19 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
20 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
21 import static android.bluetooth.BluetoothProfile.EXTRA_PREVIOUS_STATE;
22 import static android.bluetooth.BluetoothProfile.EXTRA_STATE;
23 import static android.bluetooth.BluetoothProfile.STATE_CONNECTED;
24 import static android.bluetooth.BluetoothProfile.STATE_CONNECTING;
25 import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED;
26 import static android.content.pm.PackageManager.FEATURE_WATCH;
27 
28 import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
29 import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra;
30 
31 import static com.android.bluetooth.TestUtils.MockitoRule;
32 import static com.android.bluetooth.TestUtils.getTestDevice;
33 import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.AT_OK;
34 import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.ENTER_PRIVATE_MODE;
35 import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.EXPLICIT_CALL_TRANSFER;
36 import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.VOICE_RECOGNITION_START;
37 import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.VOICE_RECOGNITION_STOP;
38 
39 import static com.google.common.truth.Truth.assertThat;
40 
41 import static org.mockito.Mockito.*;
42 
43 import android.app.BroadcastOptions;
44 import android.bluetooth.BluetoothAdapter;
45 import android.bluetooth.BluetoothAssignedNumbers;
46 import android.bluetooth.BluetoothDevice;
47 import android.bluetooth.BluetoothHeadsetClient;
48 import android.bluetooth.BluetoothHeadsetClientCall;
49 import android.bluetooth.BluetoothSinkAudioPolicy;
50 import android.bluetooth.BluetoothStatusCodes;
51 import android.content.Intent;
52 import android.content.pm.PackageManager;
53 import android.content.res.Resources;
54 import android.media.AudioManager;
55 import android.os.Bundle;
56 import android.os.Looper;
57 import android.os.Message;
58 import android.platform.test.annotations.EnableFlags;
59 import android.platform.test.flag.junit.SetFlagsRule;
60 import android.util.Pair;
61 
62 import androidx.test.filters.SmallTest;
63 import androidx.test.runner.AndroidJUnit4;
64 
65 import com.android.bluetooth.R;
66 import com.android.bluetooth.TestLooper;
67 import com.android.bluetooth.btservice.AdapterService;
68 import com.android.bluetooth.btservice.RemoteDevices;
69 import com.android.bluetooth.flags.Flags;
70 import com.android.bluetooth.hfp.HeadsetService;
71 
72 import org.hamcrest.Matcher;
73 import org.hamcrest.core.AllOf;
74 import org.junit.After;
75 import org.junit.Before;
76 import org.junit.Rule;
77 import org.junit.Test;
78 import org.junit.runner.RunWith;
79 import org.mockito.ArgumentCaptor;
80 import org.mockito.InOrder;
81 import org.mockito.Mock;
82 import org.mockito.hamcrest.MockitoHamcrest;
83 
84 import java.util.List;
85 import java.util.Set;
86 
87 /** Test cases for {@link HeadsetClientStateMachine}. */
88 @SmallTest
89 @RunWith(AndroidJUnit4.class)
90 public class HeadsetClientStateMachineTest {
91     private final BluetoothDevice mTestDevice = getTestDevice(42);
92 
93     private TestHeadsetClientStateMachine mHeadsetClientStateMachine;
94     private InOrder mInOrder;
95     private TestLooper mTestLooper;
96 
97     @Rule public final MockitoRule mMockitoRule = new MockitoRule();
98 
99     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
100 
101     @Mock private AdapterService mAdapterService;
102     @Mock private Resources mMockHfpResources;
103     @Mock private HeadsetService mHeadsetService;
104     @Mock private HeadsetClientService mHeadsetClientService;
105     @Mock private AudioManager mAudioManager;
106     @Mock private RemoteDevices mRemoteDevices;
107     @Mock private PackageManager mPackageManager;
108     @Mock private NativeInterface mNativeInterface;
109 
110     @Before
setUp()111     public void setUp() throws Exception {
112         mInOrder = inOrder(mHeadsetClientService);
113 
114         // Set a valid volume
115         doReturn(2).when(mAudioManager).getStreamVolume(anyInt());
116         doReturn(10).when(mAudioManager).getStreamMaxVolume(anyInt());
117         doReturn(1).when(mAudioManager).getStreamMinVolume(anyInt());
118 
119         doReturn(mAudioManager).when(mHeadsetClientService).getAudioManager();
120         doReturn(mMockHfpResources).when(mHeadsetClientService).getResources();
121         doReturn(mPackageManager).when(mHeadsetClientService).getPackageManager();
122         doReturn(CONNECTION_POLICY_ALLOWED).when(mHeadsetClientService).getConnectionPolicy(any());
123 
124         doReturn(true).when(mMockHfpResources).getBoolean(eq(R.bool.hfp_clcc_poll_during_call));
125         doReturn(2000)
126                 .when(mMockHfpResources)
127                 .getInteger(eq(R.integer.hfp_clcc_poll_interval_during_call));
128 
129         doReturn(mRemoteDevices).when(mAdapterService).getRemoteDevices();
130         doReturn(true).when(mNativeInterface).sendAndroidAt(any(), anyString());
131 
132         doReturn(true).when(mNativeInterface).disconnect(any(BluetoothDevice.class));
133 
134         mTestLooper = new TestLooper();
135         mHeadsetClientStateMachine =
136                 new TestHeadsetClientStateMachine(
137                         mAdapterService,
138                         mHeadsetClientService,
139                         mHeadsetService,
140                         mTestLooper.getLooper(),
141                         mNativeInterface);
142         mHeadsetClientStateMachine.start();
143         mTestLooper.dispatchAll();
144     }
145 
146     @After
tearDown()147     public void tearDown() throws Exception {
148         mTestLooper.dispatchAll();
149         mHeadsetClientStateMachine.allowConnect = null;
150         mHeadsetClientStateMachine.doQuit();
151         mTestLooper.dispatchAll();
152         verifyNoMoreInteractions(mHeadsetService);
153     }
154 
155     /** Test that default state is disconnected */
156     @Test
testDefaultDisconnectedState()157     public void testDefaultDisconnectedState() {
158         assertThat(mHeadsetClientStateMachine.getConnectionState(null))
159                 .isEqualTo(STATE_DISCONNECTED);
160     }
161 
162     /** Test that an incoming connection with low priority is rejected */
163     @Test
testIncomingPriorityReject()164     public void testIncomingPriorityReject() {
165         doReturn(CONNECTION_POLICY_FORBIDDEN)
166                 .when(mHeadsetClientService)
167                 .getConnectionPolicy(any());
168 
169         // Inject an event for when incoming connection is requested
170         StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
171         connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
172         connStCh.device = mTestDevice;
173         sendMessage(StackEvent.STACK_EVENT, connStCh);
174 
175         // Verify that only DISCONNECTED -> DISCONNECTED broadcast is fired
176         verifySendBroadcastMultiplePermissions(
177                 hasAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED),
178                 hasExtra(EXTRA_STATE, STATE_DISCONNECTED),
179                 hasExtra(EXTRA_PREVIOUS_STATE, STATE_DISCONNECTED));
180         assertThat(mHeadsetClientStateMachine.getCurrentState())
181                 .isInstanceOf(HeadsetClientStateMachine.Disconnected.class);
182     }
183 
184     /** Test that an incoming connection with high priority is accepted */
185     @Test
testIncomingPriorityAccept()186     public void testIncomingPriorityAccept() {
187         // Inject an event for when incoming connection is requested
188         StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
189         connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
190         connStCh.device = mTestDevice;
191         sendMessage(StackEvent.STACK_EVENT, connStCh);
192 
193         // Verify that one connection state broadcast is executed
194         verifySendBroadcastMultiplePermissions(hasExtra(EXTRA_STATE, STATE_CONNECTING));
195 
196         assertThat(mHeadsetClientStateMachine.getCurrentState())
197                 .isInstanceOf(HeadsetClientStateMachine.Connecting.class);
198 
199         // Send a message to trigger SLC connection
200         StackEvent slcEvent = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
201         slcEvent.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED;
202         slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS;
203         slcEvent.device = mTestDevice;
204         sendMessage(StackEvent.STACK_EVENT, slcEvent);
205 
206         setUpAndroidAt(false);
207 
208         // Verify that one connection state broadcast is executed
209         verifySendBroadcastMultiplePermissions(hasExtra(EXTRA_STATE, STATE_CONNECTED));
210         assertThat(mHeadsetClientStateMachine.getCurrentState())
211                 .isInstanceOf(HeadsetClientStateMachine.Connected.class);
212         verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
213     }
214 
215     /** Test that an incoming connection that times out */
216     @Test
testIncomingTimeout()217     public void testIncomingTimeout() {
218         // Inject an event for when incoming connection is requested
219         StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
220         connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
221         connStCh.device = mTestDevice;
222         sendMessage(StackEvent.STACK_EVENT, connStCh);
223 
224         // Verify that one connection state broadcast is executed
225         verifySendBroadcastMultiplePermissions(hasExtra(EXTRA_STATE, STATE_CONNECTING));
226         assertThat(mHeadsetClientStateMachine.getCurrentState())
227                 .isInstanceOf(HeadsetClientStateMachine.Connecting.class);
228 
229         // Trigger timeout
230         mTestLooper.moveTimeForward(HeadsetClientStateMachine.CONNECTING_TIMEOUT_MS);
231         mTestLooper.dispatchAll();
232         verifySendBroadcastMultiplePermissions(hasExtra(EXTRA_STATE, STATE_DISCONNECTED));
233         assertThat(mHeadsetClientStateMachine.getCurrentState())
234                 .isInstanceOf(HeadsetClientStateMachine.Disconnected.class);
235         verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(false));
236     }
237 
processAndroidSlcCommand(String command)238     private boolean processAndroidSlcCommand(String command) {
239         return mHeadsetClientStateMachine.processAndroidSlcCommand(command, mTestDevice);
240     }
241 
242     @Test
testProcessAndroidSlcCommand()243     public void testProcessAndroidSlcCommand() {
244         initToConnectedState();
245 
246         // True on correct AT command and BluetoothDevice
247         assertThat(processAndroidSlcCommand("+ANDROID: (SINKAUDIOPOLICY)")).isTrue();
248         assertThat(processAndroidSlcCommand("+ANDROID: ()")).isTrue();
249         assertThat(processAndroidSlcCommand("+ANDROID: (,,,)")).isTrue();
250         assertThat(processAndroidSlcCommand("+ANDROID: (SINKAUDIOPOLICY),(OTHERFEATURE)")).isTrue();
251         assertThat(
252                         processAndroidSlcCommand(
253                                 "+ANDROID: (SINKAUDIOPOLICY),(OTHERFEATURE,1,2,3),(1,2,3)"))
254                 .isTrue();
255         assertThat(processAndroidSlcCommand("+ANDROID: 123")).isTrue();
256         assertThat(processAndroidSlcCommand("+ANDROID: ")).isTrue();
257 
258         // False on incorrect AT command format
259         assertThat(processAndroidSlcCommand("+ANDROID= (SINKAUDIOPOLICY)")).isFalse();
260         assertThat(processAndroidSlcCommand("RANDOM ^%$# STRING")).isFalse();
261         assertThat(processAndroidSlcCommand("")).isFalse();
262 
263         // False on incorrect BluetoothDevice
264         assertThat(
265                         mHeadsetClientStateMachine.processAndroidSlcCommand(
266                                 "+ANDROID: (SINKAUDIOPOLICY)", getTestDevice(123)))
267                 .isFalse();
268     }
269 
270     @Test
testProcessAndroidSlcCommand_checkSinkAudioPolicy()271     public void testProcessAndroidSlcCommand_checkSinkAudioPolicy() {
272         initToConnectedState();
273 
274         mHeadsetClientStateMachine.setAudioPolicyRemoteSupported(false);
275         assertThat(processAndroidSlcCommand("RANDOM ^%$# STRING")).isFalse();
276         assertThat(mHeadsetClientStateMachine.getAudioPolicyRemoteSupported())
277                 .isEqualTo(BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
278 
279         mHeadsetClientStateMachine.setAudioPolicyRemoteSupported(false);
280         assertThat(processAndroidSlcCommand("+ANDROID= (SINKAUDIOPOLICY)")).isFalse();
281         assertThat(mHeadsetClientStateMachine.getAudioPolicyRemoteSupported())
282                 .isEqualTo(BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
283 
284         mHeadsetClientStateMachine.setAudioPolicyRemoteSupported(false);
285         assertThat(processAndroidSlcCommand("+ANDROID: (SINKAUDIOPOLICY)")).isTrue();
286         assertThat(mHeadsetClientStateMachine.getAudioPolicyRemoteSupported())
287                 .isEqualTo(BluetoothStatusCodes.FEATURE_SUPPORTED);
288     }
289 
290     /** Test that In Band Ringtone information is relayed from phone. */
291     @Test
testInBandRingtone()292     public void testInBandRingtone() {
293         assertThat(mHeadsetClientStateMachine.getInBandRing()).isFalse();
294 
295         // Inject an event for when incoming connection is requested
296         StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
297         connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
298         connStCh.device = mTestDevice;
299         sendMessage(StackEvent.STACK_EVENT, connStCh);
300 
301         // Verify that one connection state broadcast is executed
302         verifySendBroadcastMultiplePermissions(hasExtra(EXTRA_STATE, STATE_CONNECTING));
303 
304         // Send a message to trigger SLC connection
305         StackEvent slcEvent = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
306         slcEvent.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED;
307         slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS;
308         slcEvent.device = mTestDevice;
309         sendMessage(StackEvent.STACK_EVENT, slcEvent);
310 
311         setUpAndroidAt(false);
312 
313         verifySendBroadcastMultiplePermissions(hasExtra(EXTRA_STATE, STATE_CONNECTED));
314 
315         verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
316 
317         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_IN_BAND_RINGTONE);
318         event.valueInt = 0;
319         event.device = mTestDevice;
320 
321         // Enable In Band Ring and verify state gets propagated.
322         StackEvent eventInBandRing = new StackEvent(StackEvent.EVENT_TYPE_IN_BAND_RINGTONE);
323         eventInBandRing.valueInt = 1;
324         eventInBandRing.device = mTestDevice;
325         sendMessage(StackEvent.STACK_EVENT, eventInBandRing);
326         verifySendBroadcast(hasExtra(BluetoothHeadsetClient.EXTRA_IN_BAND_RING, 1));
327         assertThat(mHeadsetClientStateMachine.getInBandRing()).isTrue();
328 
329         // Simulate a new incoming phone call
330         StackEvent eventCallStatusUpdated = new StackEvent(StackEvent.EVENT_TYPE_CLIP);
331         sendMessage(StackEvent.STACK_EVENT, eventCallStatusUpdated);
332         mInOrder.verify(mHeadsetClientService, never())
333                 .sendBroadcast(any(Intent.class), anyString(), any(Bundle.class));
334 
335         // Provide information about the new call
336         StackEvent eventIncomingCall = new StackEvent(StackEvent.EVENT_TYPE_CURRENT_CALLS);
337         eventIncomingCall.valueInt = 1; // index
338         eventIncomingCall.valueInt2 = 1; // direction
339         eventIncomingCall.valueInt3 = 4; // state
340         eventIncomingCall.valueInt4 = 0; // multi party
341         eventIncomingCall.valueString = "5551212"; // phone number
342         eventIncomingCall.device = mTestDevice;
343 
344         sendMessage(StackEvent.STACK_EVENT, eventIncomingCall);
345         mInOrder.verify(mHeadsetClientService, never())
346                 .sendBroadcast(any(Intent.class), anyString(), any(Bundle.class));
347 
348         // Signal that the complete list of calls was received.
349         StackEvent eventCommandStatus = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT);
350         eventCommandStatus.valueInt = AT_OK;
351         sendMessage(StackEvent.STACK_EVENT, eventCommandStatus);
352 
353         ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
354         mInOrder.verify(mHeadsetClientService)
355                 .sendBroadcast(intentArgument.capture(), anyString(), any(Bundle.class));
356         // Verify that the new call is being registered with the inBandRing flag set.
357         HfpClientCall clientCall =
358                 (HfpClientCall)
359                         intentArgument
360                                 .getValue()
361                                 .getParcelableExtra(BluetoothHeadsetClient.EXTRA_CALL);
362         assertThat(clientCall.isInBandRing()).isTrue();
363 
364         // Disable In Band Ring and verify state gets propagated.
365         eventInBandRing.valueInt = 0;
366         sendMessage(StackEvent.STACK_EVENT, eventInBandRing);
367         verifySendBroadcast(hasExtra(BluetoothHeadsetClient.EXTRA_IN_BAND_RING, 0));
368         assertThat(mHeadsetClientStateMachine.getInBandRing()).isFalse();
369     }
370 
371     /** Test that wearables use {@code BluetoothHeadsetClientCall} in intent. */
372     @Test
testWearablesUseBluetoothHeadsetClientCallInIntent()373     public void testWearablesUseBluetoothHeadsetClientCallInIntent() {
374         // Specify the watch form factor when package manager is asked
375         doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_WATCH);
376 
377         // Skip over the Android AT commands to test this code path
378         doReturn(false).when(mNativeInterface).sendAndroidAt(any(), anyString());
379 
380         // Send an incoming connection event
381         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
382         event.device = mTestDevice;
383         event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
384         sendMessage(StackEvent.STACK_EVENT, event);
385 
386         // Send a message to trigger service level connection using the required ECS feature
387         event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
388         event.device = mTestDevice;
389         event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED;
390         event.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS;
391         sendMessage(StackEvent.STACK_EVENT, event);
392 
393         // Dial a phone call, which will fail as @{code dial} method is not specified in @{code
394         // mNativeInterface} mock and trigger a call state changed broadcast
395         sendMessage(
396                 HeadsetClientStateMachine.DIAL_NUMBER,
397                 new HfpClientCall(
398                         mTestDevice,
399                         0,
400                         HfpClientCall.CALL_STATE_WAITING,
401                         "1",
402                         false,
403                         false,
404                         false));
405 
406         // Verify the broadcast
407         ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
408         mInOrder.verify(mHeadsetClientService)
409                 .sendBroadcast(intentArgument.capture(), anyString(), any(Bundle.class));
410 
411         // Verify that the parcelable extra has a legacy {@code BluetoothHeadsetClientCall} type for
412         // wearables.
413         Object clientCall =
414                 (Object)
415                         intentArgument
416                                 .getValue()
417                                 .getParcelableExtra(BluetoothHeadsetClient.EXTRA_CALL);
418         assertThat(clientCall).isInstanceOf(BluetoothHeadsetClientCall.class);
419 
420         // To satisfy the @After verification
421         verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
422     }
423 
424     /* Utility function to simulate HfpClient is connected. */
setUpHfpClientConnection()425     private void setUpHfpClientConnection() {
426         // Trigger an incoming connection is requested
427         StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
428         connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
429         connStCh.device = mTestDevice;
430         sendMessage(StackEvent.STACK_EVENT, connStCh);
431         verifySendBroadcastMultiplePermissions(hasExtra(EXTRA_STATE, STATE_CONNECTING));
432     }
433 
434     /* Utility function to simulate SLC connection. */
setUpServiceLevelConnection()435     private void setUpServiceLevelConnection() {
436         setUpServiceLevelConnection(false);
437     }
438 
setUpServiceLevelConnection(boolean androidAtSupported)439     private void setUpServiceLevelConnection(boolean androidAtSupported) {
440         // Trigger SLC connection
441         StackEvent slcEvent = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
442         slcEvent.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED;
443         slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS;
444         slcEvent.valueInt2 |= HeadsetClientHalConstants.PEER_FEAT_HF_IND;
445         slcEvent.device = mTestDevice;
446         sendMessage(StackEvent.STACK_EVENT, slcEvent);
447 
448         setUpAndroidAt(androidAtSupported);
449 
450         verifySendBroadcastMultiplePermissions(hasExtra(EXTRA_STATE, STATE_CONNECTED));
451         assertThat(mHeadsetClientStateMachine.getCurrentState())
452                 .isInstanceOf(HeadsetClientStateMachine.Connected.class);
453         verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
454     }
455 
456     /**
457      * Set up and verify AT Android related commands and events. Make sure this method is invoked
458      * after SLC is setup.
459      */
setUpAndroidAt(boolean androidAtSupported)460     private void setUpAndroidAt(boolean androidAtSupported) {
461         verify(mNativeInterface).sendAndroidAt(mTestDevice, "+ANDROID=?");
462         if (androidAtSupported) {
463             // inject Android AT features
464             StackEvent unknownEvt = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
465             unknownEvt.valueString = "+ANDROID: (SINKAUDIOPOLICY)";
466             unknownEvt.device = mTestDevice;
467             sendMessage(StackEvent.STACK_EVENT, unknownEvt);
468 
469             // receive CMD_RESULT OK after the Android AT command from remote
470             StackEvent cmdResEvt = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT);
471             cmdResEvt.valueInt = StackEvent.CMD_RESULT_TYPE_OK;
472             cmdResEvt.device = mTestDevice;
473             sendMessage(StackEvent.STACK_EVENT, cmdResEvt);
474 
475             assertThat(mHeadsetClientStateMachine.getAudioPolicyRemoteSupported())
476                     .isEqualTo(BluetoothStatusCodes.FEATURE_SUPPORTED);
477         } else {
478             // receive CMD_RESULT CME_ERROR due to remote not supporting Android AT
479             StackEvent cmdResEvt = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT);
480             cmdResEvt.valueInt = StackEvent.CMD_RESULT_TYPE_CME_ERROR;
481             cmdResEvt.device = mTestDevice;
482             sendMessage(StackEvent.STACK_EVENT, cmdResEvt);
483 
484             assertThat(mHeadsetClientStateMachine.getAudioPolicyRemoteSupported())
485                     .isEqualTo(BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
486         }
487     }
488 
489     /* Utility function: supported AT command should lead to native call */
runSupportedVendorAtCommand(String atCommand, int vendorId)490     private void runSupportedVendorAtCommand(String atCommand, int vendorId) {
491         setUpHfpClientConnection();
492         setUpServiceLevelConnection();
493 
494         sendMessage(HeadsetClientStateMachine.SEND_VENDOR_AT_COMMAND, vendorId, 0, atCommand);
495 
496         verify(mNativeInterface)
497                 .sendATCmd(
498                         mTestDevice,
499                         HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_VENDOR_SPECIFIC_CMD,
500                         0,
501                         0,
502                         atCommand);
503     }
504 
505     /** Test: supported vendor specific command: set operation */
506     @Test
testSupportedVendorAtCommandSet()507     public void testSupportedVendorAtCommandSet() {
508         int vendorId = BluetoothAssignedNumbers.APPLE;
509         String atCommand = "+XAPL=ABCD-1234-0100,100";
510         runSupportedVendorAtCommand(atCommand, vendorId);
511     }
512 
513     /** Test: supported vendor specific command: read operation */
514     @Test
testSupportedVendorAtCommandRead()515     public void testSupportedVendorAtCommandRead() {
516         int vendorId = BluetoothAssignedNumbers.APPLE;
517         String atCommand = "+APLSIRI?";
518         runSupportedVendorAtCommand(atCommand, vendorId);
519     }
520 
521     /* utility function: unsupported vendor specific command shall be filtered. */
runUnsupportedVendorAtCommand(String atCommand, int vendorId)522     public void runUnsupportedVendorAtCommand(String atCommand, int vendorId) {
523         setUpHfpClientConnection();
524         setUpServiceLevelConnection();
525 
526         sendMessage(HeadsetClientStateMachine.SEND_VENDOR_AT_COMMAND, vendorId, 0, atCommand);
527 
528         verify(mNativeInterface, never()).sendATCmd(any(), anyInt(), anyInt(), anyInt(), any());
529     }
530 
531     /** Test: unsupported vendor specific command shall be filtered: bad command code */
532     @Test
testUnsupportedVendorAtCommandBadCode()533     public void testUnsupportedVendorAtCommandBadCode() {
534         String atCommand = "+XAAPL=ABCD-1234-0100,100";
535         int vendorId = BluetoothAssignedNumbers.APPLE;
536         runUnsupportedVendorAtCommand(atCommand, vendorId);
537     }
538 
539     /** Test: unsupported vendor specific command shall be filtered: no back to back command */
540     @Test
testUnsupportedVendorAtCommandBackToBack()541     public void testUnsupportedVendorAtCommandBackToBack() {
542         String atCommand = "+XAPL=ABCD-1234-0100,100; +XAPL=ab";
543         int vendorId = BluetoothAssignedNumbers.APPLE;
544         runUnsupportedVendorAtCommand(atCommand, vendorId);
545     }
546 
547     /* Utility test function: supported vendor specific event
548      * shall lead to broadcast intent
549      */
runSupportedVendorEvent( int vendorId, String vendorEventCode, String vendorEventArgument)550     private void runSupportedVendorEvent(
551             int vendorId, String vendorEventCode, String vendorEventArgument) {
552         setUpHfpClientConnection();
553         setUpServiceLevelConnection();
554 
555         // Simulate a known event arrive
556         String vendorEvent = vendorEventCode + vendorEventArgument;
557         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
558         event.device = mTestDevice;
559         event.valueString = vendorEvent;
560         sendMessage(StackEvent.STACK_EVENT, event);
561 
562         // Validate broadcast intent
563         verifySendBroadcast(
564                 hasAction(BluetoothHeadsetClient.ACTION_VENDOR_SPECIFIC_HEADSETCLIENT_EVENT),
565                 hasExtra(BluetoothHeadsetClient.EXTRA_VENDOR_ID, vendorId),
566                 hasExtra(BluetoothHeadsetClient.EXTRA_VENDOR_EVENT_CODE, vendorEventCode),
567                 hasExtra(BluetoothHeadsetClient.EXTRA_VENDOR_EVENT_FULL_ARGS, vendorEvent));
568     }
569 
570     /** Test: supported vendor specific response: response to read command */
571     @Test
testSupportedVendorEventReadResponse()572     public void testSupportedVendorEventReadResponse() {
573         final int vendorId = BluetoothAssignedNumbers.APPLE;
574         final String vendorResponseCode = "+XAPL=";
575         final String vendorResponseArgument = "iPhone,2";
576         runSupportedVendorEvent(vendorId, vendorResponseCode, vendorResponseArgument);
577     }
578 
579     /** Test: supported vendor specific response: response to test command */
580     @Test
testSupportedVendorEventTestResponse()581     public void testSupportedVendorEventTestResponse() {
582         final int vendorId = BluetoothAssignedNumbers.APPLE;
583         final String vendorResponseCode = "+APLSIRI:";
584         final String vendorResponseArgumentWithSpace = "  2";
585         runSupportedVendorEvent(vendorId, vendorResponseCode, vendorResponseArgumentWithSpace);
586     }
587 
588     /* Utility test function: unsupported vendor specific response shall be filtered out*/
runUnsupportedVendorEvent( int vendorId, String vendorEventCode, String vendorEventArgument)589     public void runUnsupportedVendorEvent(
590             int vendorId, String vendorEventCode, String vendorEventArgument) {
591         setUpHfpClientConnection();
592         setUpServiceLevelConnection();
593 
594         // Simulate an unknown event arrive
595         String vendorEvent = vendorEventCode + vendorEventArgument;
596         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
597         event.device = mTestDevice;
598         event.valueString = vendorEvent;
599         sendMessage(StackEvent.STACK_EVENT, event);
600 
601         // Validate no broadcast intent
602         verify(mHeadsetClientService, atMost(2))
603                 .sendBroadcast(any(), anyString(), any(Bundle.class));
604     }
605 
606     /** Test unsupported vendor response: bad read response */
607     @Test
testUnsupportedVendorEventBadReadResponse()608     public void testUnsupportedVendorEventBadReadResponse() {
609         final int vendorId = BluetoothAssignedNumbers.APPLE;
610         final String vendorResponseCode = "+XAAPL=";
611         final String vendorResponseArgument = "iPhone,2";
612         runUnsupportedVendorEvent(vendorId, vendorResponseCode, vendorResponseArgument);
613     }
614 
615     /** Test unsupported vendor response: bad test response */
616     @Test
testUnsupportedVendorEventBadTestResponse()617     public void testUnsupportedVendorEventBadTestResponse() {
618         final int vendorId = BluetoothAssignedNumbers.APPLE;
619         final String vendorResponseCode = "+AAPLSIRI:";
620         final String vendorResponseArgument = "2";
621         runUnsupportedVendorEvent(vendorId, vendorResponseCode, vendorResponseArgument);
622     }
623 
624     /** Test voice recognition state change broadcast. */
625     @Test
testVoiceRecognitionStateChange()626     public void testVoiceRecognitionStateChange() {
627         doReturn(true).when(mNativeInterface).startVoiceRecognition(any(BluetoothDevice.class));
628         doReturn(true).when(mNativeInterface).stopVoiceRecognition(any(BluetoothDevice.class));
629 
630         setUpHfpClientConnection();
631         setUpServiceLevelConnection();
632 
633         // Simulate a voice recognition start
634         sendMessage(VOICE_RECOGNITION_START);
635 
636         // Signal that the complete list of actions was received.
637         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT);
638         event.device = mTestDevice;
639         event.valueInt = AT_OK;
640         sendMessage(StackEvent.STACK_EVENT, event);
641 
642         verifySendBroadcast(
643                 hasAction(BluetoothHeadsetClient.ACTION_AG_EVENT),
644                 hasExtra(
645                         BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION,
646                         HeadsetClientHalConstants.VR_STATE_STARTED));
647 
648         // Simulate a voice recognition stop
649         sendMessage(VOICE_RECOGNITION_STOP);
650 
651         // Signal that the complete list of actions was received.
652         event = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT);
653         event.device = mTestDevice;
654         event.valueInt = AT_OK;
655         sendMessage(StackEvent.STACK_EVENT, event);
656 
657         verifySendBroadcast(
658                 hasAction(BluetoothHeadsetClient.ACTION_AG_EVENT),
659                 hasExtra(
660                         BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION,
661                         HeadsetClientHalConstants.VR_STATE_STOPPED));
662     }
663 
664     /** Test send BIEV command */
665     @Test
testSendBIEVCommand()666     public void testSendBIEVCommand() {
667         setUpHfpClientConnection();
668         setUpServiceLevelConnection();
669 
670         int indicator_id = 2;
671         int indicator_value = 50;
672 
673         sendMessage(HeadsetClientStateMachine.SEND_BIEV, indicator_id, indicator_value);
674 
675         verify(mNativeInterface)
676                 .sendATCmd(
677                         mTestDevice,
678                         HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_BIEV,
679                         indicator_id,
680                         indicator_value,
681                         null);
682     }
683 
684     /**
685      * Test state machine shall try to send AT+BIEV command to AG to update an init battery level.
686      */
687     @Test
testSendBatteryUpdateIndicatorWhenConnect()688     public void testSendBatteryUpdateIndicatorWhenConnect() {
689         setUpHfpClientConnection();
690         setUpServiceLevelConnection();
691 
692         verify(mHeadsetClientService).updateBatteryLevel();
693     }
694 
695     @Test
testBroadcastAudioState()696     public void testBroadcastAudioState() {
697         mHeadsetClientStateMachine.broadcastAudioState(
698                 mTestDevice,
699                 BluetoothHeadsetClient.STATE_AUDIO_CONNECTED,
700                 BluetoothHeadsetClient.STATE_AUDIO_CONNECTING);
701 
702         mInOrder.verify(mHeadsetClientService).sendBroadcast(any(), any(), any());
703     }
704 
705     @Test
testCallsInState()706     public void testCallsInState() {
707         HfpClientCall call =
708                 new HfpClientCall(
709                         mTestDevice, 0, HfpClientCall.CALL_STATE_WAITING, "1", false, false, false);
710         mHeadsetClientStateMachine.mCalls.put(0, call);
711 
712         assertThat(mHeadsetClientStateMachine.callsInState(HfpClientCall.CALL_STATE_WAITING))
713                 .isEqualTo(1);
714     }
715 
716     @Test
testEnterPrivateMode()717     public void testEnterPrivateMode() {
718         HfpClientCall call =
719                 new HfpClientCall(
720                         mTestDevice, 0, HfpClientCall.CALL_STATE_ACTIVE, "1", true, false, false);
721         mHeadsetClientStateMachine.mCalls.put(0, call);
722         doReturn(true)
723                 .when(mNativeInterface)
724                 .handleCallAction(null, HeadsetClientHalConstants.CALL_ACTION_CHLD_2X, 0);
725 
726         mHeadsetClientStateMachine.enterPrivateMode(0);
727 
728         Pair expectedPair = new Pair<Integer, Object>(ENTER_PRIVATE_MODE, call);
729         assertThat(mHeadsetClientStateMachine.mQueuedActions.peek()).isEqualTo(expectedPair);
730     }
731 
732     @Test
testExplicitCallTransfer()733     public void testExplicitCallTransfer() {
734         HfpClientCall callOne =
735                 new HfpClientCall(
736                         mTestDevice, 0, HfpClientCall.CALL_STATE_ACTIVE, "1", true, false, false);
737         HfpClientCall callTwo =
738                 new HfpClientCall(
739                         mTestDevice, 0, HfpClientCall.CALL_STATE_ACTIVE, "1", true, false, false);
740         mHeadsetClientStateMachine.mCalls.put(0, callOne);
741         mHeadsetClientStateMachine.mCalls.put(1, callTwo);
742         doReturn(true)
743                 .when(mNativeInterface)
744                 .handleCallAction(null, HeadsetClientHalConstants.CALL_ACTION_CHLD_4, -1);
745 
746         mHeadsetClientStateMachine.explicitCallTransfer();
747 
748         Pair expectedPair = new Pair<Integer, Object>(EXPLICIT_CALL_TRANSFER, 0);
749         assertThat(mHeadsetClientStateMachine.mQueuedActions.peek()).isEqualTo(expectedPair);
750     }
751 
752     @Test
testSetAudioRouteAllowed()753     public void testSetAudioRouteAllowed() {
754         mHeadsetClientStateMachine.setAudioRouteAllowed(true);
755 
756         assertThat(mHeadsetClientStateMachine.getAudioRouteAllowed()).isTrue();
757 
758         // Case 1: if remote is not supported
759         // Expect: Should not send +ANDROID to remote
760         mHeadsetClientStateMachine.mCurrentDevice = mTestDevice;
761         mHeadsetClientStateMachine.setAudioPolicyRemoteSupported(false);
762         verify(mNativeInterface, never())
763                 .sendAndroidAt(mTestDevice, "+ANDROID=SINKAUDIOPOLICY,1,0,0");
764 
765         // Case 2: if remote is supported and mForceSetAudioPolicyProperty is false
766         // Expect: Should send +ANDROID=SINKAUDIOPOLICY,1,0,0 to remote
767         mHeadsetClientStateMachine.setAudioPolicyRemoteSupported(true);
768         mHeadsetClientStateMachine.setForceSetAudioPolicyProperty(false);
769         mHeadsetClientStateMachine.setAudioRouteAllowed(true);
770         verify(mNativeInterface).sendAndroidAt(mTestDevice, "+ANDROID=SINKAUDIOPOLICY,1,0,0");
771 
772         mHeadsetClientStateMachine.setAudioRouteAllowed(false);
773         verify(mNativeInterface).sendAndroidAt(mTestDevice, "+ANDROID=SINKAUDIOPOLICY,2,0,0");
774 
775         // Case 3: if remote is supported and mForceSetAudioPolicyProperty is true
776         // Expect: Should send +ANDROID=SINKAUDIOPOLICY,1,2,1 to remote
777         mHeadsetClientStateMachine.setForceSetAudioPolicyProperty(true);
778         mHeadsetClientStateMachine.setAudioRouteAllowed(true);
779         verify(mNativeInterface).sendAndroidAt(mTestDevice, "+ANDROID=SINKAUDIOPOLICY,1,1,1");
780     }
781 
782     @Test
testGetAudioState_withCurrentDeviceNull()783     public void testGetAudioState_withCurrentDeviceNull() {
784         assertThat(mHeadsetClientStateMachine.mCurrentDevice).isNull();
785 
786         assertThat(mHeadsetClientStateMachine.getAudioState(mTestDevice))
787                 .isEqualTo(BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED);
788     }
789 
790     @Test
testGetAudioState_withCurrentDeviceNotNull()791     public void testGetAudioState_withCurrentDeviceNotNull() {
792         int audioState = 1;
793         mHeadsetClientStateMachine.mAudioState = audioState;
794         mHeadsetClientStateMachine.mCurrentDevice = mTestDevice;
795 
796         assertThat(mHeadsetClientStateMachine.getAudioState(mTestDevice)).isEqualTo(audioState);
797     }
798 
799     @Test
testGetCall_withMatchingState()800     public void testGetCall_withMatchingState() {
801         HfpClientCall call =
802                 new HfpClientCall(
803                         mTestDevice, 0, HfpClientCall.CALL_STATE_ACTIVE, "1", true, false, false);
804         mHeadsetClientStateMachine.mCalls.put(0, call);
805         int[] states = new int[1];
806         states[0] = HfpClientCall.CALL_STATE_ACTIVE;
807 
808         assertThat(mHeadsetClientStateMachine.getCall(states)).isEqualTo(call);
809     }
810 
811     @Test
testGetCall_withNoMatchingState()812     public void testGetCall_withNoMatchingState() {
813         HfpClientCall call =
814                 new HfpClientCall(
815                         mTestDevice, 0, HfpClientCall.CALL_STATE_WAITING, "1", true, false, false);
816         mHeadsetClientStateMachine.mCalls.put(0, call);
817         int[] states = new int[1];
818         states[0] = HfpClientCall.CALL_STATE_ACTIVE;
819 
820         assertThat(mHeadsetClientStateMachine.getCall(states)).isNull();
821     }
822 
823     @Test
testGetConnectionState_withNullDevice()824     public void testGetConnectionState_withNullDevice() {
825         assertThat(mHeadsetClientStateMachine.getConnectionState(null))
826                 .isEqualTo(STATE_DISCONNECTED);
827     }
828 
829     @Test
testGetConnectionState_withNonNullDevice()830     public void testGetConnectionState_withNonNullDevice() {
831         mHeadsetClientStateMachine.mCurrentDevice = mTestDevice;
832 
833         assertThat(mHeadsetClientStateMachine.getConnectionState(mTestDevice))
834                 .isEqualTo(STATE_DISCONNECTED);
835     }
836 
837     @Test
testGetConnectionStateFromAudioState()838     public void testGetConnectionStateFromAudioState() {
839         assertThat(
840                         HeadsetClientStateMachine.getConnectionStateFromAudioState(
841                                 BluetoothHeadsetClient.STATE_AUDIO_CONNECTED))
842                 .isEqualTo(BluetoothAdapter.STATE_CONNECTED);
843         assertThat(
844                         HeadsetClientStateMachine.getConnectionStateFromAudioState(
845                                 BluetoothHeadsetClient.STATE_AUDIO_CONNECTING))
846                 .isEqualTo(BluetoothAdapter.STATE_CONNECTING);
847         assertThat(
848                         HeadsetClientStateMachine.getConnectionStateFromAudioState(
849                                 BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED))
850                 .isEqualTo(BluetoothAdapter.STATE_DISCONNECTED);
851         int invalidAudioState = 3;
852         assertThat(HeadsetClientStateMachine.getConnectionStateFromAudioState(invalidAudioState))
853                 .isEqualTo(BluetoothAdapter.STATE_DISCONNECTED);
854     }
855 
856     @Test
testGetCurrentAgEvents()857     public void testGetCurrentAgEvents() {
858         Bundle bundle = mHeadsetClientStateMachine.getCurrentAgEvents();
859 
860         assertThat(bundle.getString(BluetoothHeadsetClient.EXTRA_SUBSCRIBER_INFO))
861                 .isEqualTo(mHeadsetClientStateMachine.mSubscriberInfo);
862     }
863 
864     @Test
testGetCurrentAgFeatures()865     public void testGetCurrentAgFeatures() {
866         mHeadsetClientStateMachine.mPeerFeatures = HeadsetClientHalConstants.PEER_FEAT_3WAY;
867         mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC;
868         Set<Integer> features = mHeadsetClientStateMachine.getCurrentAgFeatures();
869         assertThat(features.contains(HeadsetClientHalConstants.PEER_FEAT_3WAY)).isTrue();
870         assertThat(features.contains(HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC)).isTrue();
871 
872         mHeadsetClientStateMachine.mPeerFeatures = HeadsetClientHalConstants.PEER_FEAT_VREC;
873         mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_REL;
874         features = mHeadsetClientStateMachine.getCurrentAgFeatures();
875         assertThat(features.contains(HeadsetClientHalConstants.PEER_FEAT_VREC)).isTrue();
876         assertThat(features.contains(HeadsetClientHalConstants.CHLD_FEAT_REL)).isTrue();
877 
878         mHeadsetClientStateMachine.mPeerFeatures = HeadsetClientHalConstants.PEER_FEAT_REJECT;
879         mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_REL_ACC;
880         features = mHeadsetClientStateMachine.getCurrentAgFeatures();
881         assertThat(features.contains(HeadsetClientHalConstants.PEER_FEAT_REJECT)).isTrue();
882         assertThat(features.contains(HeadsetClientHalConstants.CHLD_FEAT_REL_ACC)).isTrue();
883 
884         mHeadsetClientStateMachine.mPeerFeatures = HeadsetClientHalConstants.PEER_FEAT_ECC;
885         mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_MERGE;
886         features = mHeadsetClientStateMachine.getCurrentAgFeatures();
887         assertThat(features.contains(HeadsetClientHalConstants.PEER_FEAT_ECC)).isTrue();
888         assertThat(features.contains(HeadsetClientHalConstants.CHLD_FEAT_MERGE)).isTrue();
889 
890         mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH;
891         features = mHeadsetClientStateMachine.getCurrentAgFeatures();
892         assertThat(features.contains(HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH)).isTrue();
893     }
894 
895     @Test
testGetCurrentAgFeaturesBundle()896     public void testGetCurrentAgFeaturesBundle() {
897         mHeadsetClientStateMachine.mPeerFeatures = HeadsetClientHalConstants.PEER_FEAT_3WAY;
898         mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC;
899         Bundle bundle = mHeadsetClientStateMachine.getCurrentAgFeaturesBundle();
900         assertThat(bundle.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_3WAY_CALLING))
901                 .isTrue();
902         assertThat(
903                         bundle.getBoolean(
904                                 BluetoothHeadsetClient
905                                         .EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL))
906                 .isTrue();
907 
908         mHeadsetClientStateMachine.mPeerFeatures = HeadsetClientHalConstants.PEER_FEAT_VREC;
909         mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_REL;
910         bundle = mHeadsetClientStateMachine.getCurrentAgFeaturesBundle();
911         assertThat(bundle.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_VOICE_RECOGNITION))
912                 .isTrue();
913         assertThat(
914                         bundle.getBoolean(
915                                 BluetoothHeadsetClient
916                                         .EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL))
917                 .isTrue();
918 
919         mHeadsetClientStateMachine.mPeerFeatures = HeadsetClientHalConstants.PEER_FEAT_REJECT;
920         mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_REL_ACC;
921         bundle = mHeadsetClientStateMachine.getCurrentAgFeaturesBundle();
922         assertThat(bundle.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_REJECT_CALL)).isTrue();
923         assertThat(bundle.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT))
924                 .isTrue();
925 
926         mHeadsetClientStateMachine.mPeerFeatures = HeadsetClientHalConstants.PEER_FEAT_ECC;
927         mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_MERGE;
928         bundle = mHeadsetClientStateMachine.getCurrentAgFeaturesBundle();
929         assertThat(bundle.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ECC)).isTrue();
930         assertThat(bundle.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE)).isTrue();
931 
932         mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH;
933         bundle = mHeadsetClientStateMachine.getCurrentAgFeaturesBundle();
934         assertThat(bundle.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE_AND_DETACH))
935                 .isTrue();
936     }
937 
938     @Test
testGetCurrentCalls()939     public void testGetCurrentCalls() {
940         HfpClientCall call =
941                 new HfpClientCall(
942                         mTestDevice, 0, HfpClientCall.CALL_STATE_WAITING, "1", true, false, false);
943         mHeadsetClientStateMachine.mCalls.put(0, call);
944 
945         List<HfpClientCall> currentCalls = mHeadsetClientStateMachine.getCurrentCalls();
946 
947         assertThat(currentCalls.get(0)).isEqualTo(call);
948     }
949 
assertName(int message, String message_name)950     private static void assertName(int message, String message_name) {
951         assertThat(HeadsetClientStateMachine.getMessageName(message)).isEqualTo(message_name);
952     }
953 
954     @Test
testGetMessageName()955     public void testGetMessageName() {
956         assertName(StackEvent.STACK_EVENT, "STACK_EVENT");
957         assertName(HeadsetClientStateMachine.CONNECT, "CONNECT");
958         assertName(HeadsetClientStateMachine.DISCONNECT, "DISCONNECT");
959         assertName(HeadsetClientStateMachine.CONNECT_AUDIO, "CONNECT_AUDIO");
960         assertName(HeadsetClientStateMachine.DISCONNECT_AUDIO, "DISCONNECT_AUDIO");
961         assertName(VOICE_RECOGNITION_START, "VOICE_RECOGNITION_START");
962         assertName(VOICE_RECOGNITION_STOP, "VOICE_RECOGNITION_STOP");
963         assertName(HeadsetClientStateMachine.SET_MIC_VOLUME, "SET_MIC_VOLUME");
964         assertName(HeadsetClientStateMachine.SET_SPEAKER_VOLUME, "SET_SPEAKER_VOLUME");
965         assertName(HeadsetClientStateMachine.DIAL_NUMBER, "DIAL_NUMBER");
966         assertName(HeadsetClientStateMachine.ACCEPT_CALL, "ACCEPT_CALL");
967         assertName(HeadsetClientStateMachine.REJECT_CALL, "REJECT_CALL");
968         assertName(HeadsetClientStateMachine.HOLD_CALL, "HOLD_CALL");
969         assertName(HeadsetClientStateMachine.TERMINATE_CALL, "TERMINATE_CALL");
970         assertName(ENTER_PRIVATE_MODE, "ENTER_PRIVATE_MODE");
971         assertName(HeadsetClientStateMachine.SEND_DTMF, "SEND_DTMF");
972         assertName(EXPLICIT_CALL_TRANSFER, "EXPLICIT_CALL_TRANSFER");
973         assertName(HeadsetClientStateMachine.DISABLE_NREC, "DISABLE_NREC");
974         assertName(HeadsetClientStateMachine.SEND_VENDOR_AT_COMMAND, "SEND_VENDOR_AT_COMMAND");
975         assertName(HeadsetClientStateMachine.SEND_BIEV, "SEND_BIEV");
976         assertName(HeadsetClientStateMachine.QUERY_CURRENT_CALLS, "QUERY_CURRENT_CALLS");
977         assertName(HeadsetClientStateMachine.QUERY_OPERATOR_NAME, "QUERY_OPERATOR_NAME");
978         assertName(HeadsetClientStateMachine.SUBSCRIBER_INFO, "SUBSCRIBER_INFO");
979         assertName(HeadsetClientStateMachine.CONNECTING_TIMEOUT, "CONNECTING_TIMEOUT");
980         assertName(HeadsetClientStateMachine.DISCONNECTING_TIMEOUT, "DISCONNECTING_TIMEOUT");
981         int unknownMessageInt = 55;
982         assertName(unknownMessageInt, "UNKNOWN(" + unknownMessageInt + ")");
983     }
984 
985     /**
986      * Tests and verify behavior of the case where remote device doesn't support At Android but
987      * tries to send audio policy.
988      */
989     @Test
testAndroidAtRemoteNotSupported_StateTransition_setAudioPolicy()990     public void testAndroidAtRemoteNotSupported_StateTransition_setAudioPolicy() {
991         setUpHfpClientConnection();
992         setUpServiceLevelConnection();
993 
994         BluetoothSinkAudioPolicy dummyAudioPolicy = new BluetoothSinkAudioPolicy.Builder().build();
995         mHeadsetClientStateMachine.setAudioPolicy(dummyAudioPolicy);
996         verify(mNativeInterface, never())
997                 .sendAndroidAt(mTestDevice, "+ANDROID=SINKAUDIOPOLICY,0,0,0");
998     }
999 
1000     @Test
testSetGetCallAudioPolicy()1001     public void testSetGetCallAudioPolicy() {
1002 
1003         setUpHfpClientConnection();
1004         setUpServiceLevelConnection(true);
1005 
1006         BluetoothSinkAudioPolicy dummyAudioPolicy =
1007                 new BluetoothSinkAudioPolicy.Builder()
1008                         .setCallEstablishPolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
1009                         .setActiveDevicePolicyAfterConnection(
1010                                 BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED)
1011                         .setInBandRingtonePolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
1012                         .build();
1013 
1014         // Test if not support audio policy feature
1015         mHeadsetClientStateMachine.setAudioPolicyRemoteSupported(false);
1016         mHeadsetClientStateMachine.setAudioPolicy(dummyAudioPolicy);
1017         verify(mNativeInterface, never())
1018                 .sendAndroidAt(mTestDevice, "+ANDROID=SINKAUDIOPOLICY,1,2,1");
1019         assertThat(mHeadsetClientStateMachine.mQueuedActions).isEmpty();
1020 
1021         // Test setAudioPolicy
1022         mHeadsetClientStateMachine.setAudioPolicyRemoteSupported(true);
1023         mHeadsetClientStateMachine.setAudioPolicy(dummyAudioPolicy);
1024         verify(mNativeInterface).sendAndroidAt(mTestDevice, "+ANDROID=SINKAUDIOPOLICY,1,2,1");
1025         assertThat(mHeadsetClientStateMachine.mQueuedActions).hasSize(1);
1026         mHeadsetClientStateMachine.mQueuedActions.clear();
1027 
1028         // Test if fail to sendAndroidAt
1029         doReturn(false).when(mNativeInterface).sendAndroidAt(any(), anyString());
1030         mHeadsetClientStateMachine.setAudioPolicy(dummyAudioPolicy);
1031         assertThat(mHeadsetClientStateMachine.mQueuedActions).isEmpty();
1032     }
1033 
1034     @Test
testTestDefaultAudioPolicy()1035     public void testTestDefaultAudioPolicy() {
1036         mHeadsetClientStateMachine.setForceSetAudioPolicyProperty(true);
1037         initToConnectedState();
1038 
1039         // Check if set default policy when Connecting -> Connected
1040         // The testing sys prop is 0. It is ok to check if set audio policy while leaving connecting
1041         // state.
1042         verify(mNativeInterface).sendAndroidAt(mTestDevice, "+ANDROID=SINKAUDIOPOLICY,0,0,0");
1043 
1044         // Check if won't set default policy when AudioOn -> Connected
1045         // Transit to AudioOn
1046         mHeadsetClientStateMachine.setAudioRouteAllowed(true);
1047         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
1048         event.valueInt = HeadsetClientHalConstants.AUDIO_STATE_CONNECTED;
1049         event.device = mTestDevice;
1050         sendMessage(StackEvent.STACK_EVENT, event);
1051         assertThat(mHeadsetClientStateMachine.getCurrentState())
1052                 .isInstanceOf(HeadsetClientStateMachine.AudioOn.class);
1053 
1054         // Back to Connected
1055         event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
1056         event.valueInt = HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED;
1057         event.device = mTestDevice;
1058         sendMessage(StackEvent.STACK_EVENT, event);
1059         assertThat(mHeadsetClientStateMachine.getCurrentState())
1060                 .isInstanceOf(HeadsetClientStateMachine.Connected.class);
1061 
1062         verify(mNativeInterface).sendAndroidAt(mTestDevice, "+ANDROID=SINKAUDIOPOLICY,0,0,0");
1063     }
1064 
1065     @Test
testDumpDoesNotCrash()1066     public void testDumpDoesNotCrash() {
1067         mHeadsetClientStateMachine.dump(new StringBuilder());
1068     }
1069 
1070     @Test
testProcessDisconnectMessage_onDisconnectedState()1071     public void testProcessDisconnectMessage_onDisconnectedState() {
1072         sendMessage(HeadsetClientStateMachine.DISCONNECT);
1073         assertThat(mHeadsetClientStateMachine.getConnectionState(mTestDevice))
1074                 .isEqualTo(STATE_DISCONNECTED);
1075     }
1076 
1077     @Test
testProcessConnectMessage_onDisconnectedState()1078     public void testProcessConnectMessage_onDisconnectedState() {
1079         doReturn(true).when(mNativeInterface).connect(any(BluetoothDevice.class));
1080         sendMessageAndVerifyTransition(
1081                 mHeadsetClientStateMachine.obtainMessage(
1082                         HeadsetClientStateMachine.CONNECT, mTestDevice),
1083                 HeadsetClientStateMachine.Connecting.class);
1084     }
1085 
1086     @Test
testStackEvent_toConnectingState_onDisconnectedState()1087     public void testStackEvent_toConnectingState_onDisconnectedState() {
1088         allowConnection(true);
1089         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
1090         event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
1091         event.device = mTestDevice;
1092         sendMessageAndVerifyTransition(
1093                 mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event),
1094                 HeadsetClientStateMachine.Connecting.class);
1095     }
1096 
1097     @Test
testStackEvent_toConnectingState_disallowConnection_onDisconnectedState()1098     public void testStackEvent_toConnectingState_disallowConnection_onDisconnectedState() {
1099         allowConnection(false);
1100         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
1101         event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
1102         event.device = mTestDevice;
1103         sendMessageAndVerifyTransition(
1104                 mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event),
1105                 HeadsetClientStateMachine.Disconnected.class);
1106     }
1107 
1108     @Test
testProcessConnectMessage_onConnectingState()1109     public void testProcessConnectMessage_onConnectingState() {
1110         initToConnectingState();
1111         assertThat(
1112                         mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
1113                                 HeadsetClientStateMachine.CONNECT))
1114                 .isFalse();
1115         sendMessage(HeadsetClientStateMachine.CONNECT);
1116         assertThat(
1117                         mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
1118                                 HeadsetClientStateMachine.CONNECT))
1119                 .isTrue();
1120     }
1121 
1122     @Test
testProcessStackEvent_ConnectionStateChanged_Disconnected_onConnectingState()1123     public void testProcessStackEvent_ConnectionStateChanged_Disconnected_onConnectingState() {
1124         initToConnectingState();
1125         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
1126         event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED;
1127         event.device = mTestDevice;
1128         sendMessageAndVerifyTransition(
1129                 mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event),
1130                 HeadsetClientStateMachine.Disconnected.class);
1131         verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(false));
1132     }
1133 
1134     @Test
testProcessStackEvent_ConnectionStateChanged_Connected_onConnectingState()1135     public void testProcessStackEvent_ConnectionStateChanged_Connected_onConnectingState() {
1136         initToConnectingState();
1137         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
1138         event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
1139         event.device = mTestDevice;
1140         sendMessage(StackEvent.STACK_EVENT, event);
1141         assertThat(mHeadsetClientStateMachine.getCurrentState())
1142                 .isInstanceOf(HeadsetClientStateMachine.Connecting.class);
1143     }
1144 
1145     @Test
testProcessStackEvent_ConnectionStateChanged_Connecting_onConnectingState()1146     public void testProcessStackEvent_ConnectionStateChanged_Connecting_onConnectingState() {
1147         initToConnectingState();
1148         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
1149         event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTING;
1150         event.device = mTestDevice;
1151         sendMessage(StackEvent.STACK_EVENT, event);
1152         assertThat(mHeadsetClientStateMachine.getCurrentState())
1153                 .isInstanceOf(HeadsetClientStateMachine.Connecting.class);
1154     }
1155 
1156     @Test
testProcessStackEvent_Call_onConnectingState()1157     public void testProcessStackEvent_Call_onConnectingState() {
1158         initToConnectingState();
1159         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CALL);
1160         event.valueInt = StackEvent.EVENT_TYPE_CALL;
1161         event.device = mTestDevice;
1162         assertThat(mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(StackEvent.STACK_EVENT))
1163                 .isFalse();
1164         sendMessage(StackEvent.STACK_EVENT, event);
1165         assertThat(mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(StackEvent.STACK_EVENT))
1166                 .isTrue();
1167     }
1168 
1169     @Test
testProcessStackEvent_CmdResultWithEmptyQueuedActions_onConnectingState()1170     public void testProcessStackEvent_CmdResultWithEmptyQueuedActions_onConnectingState() {
1171         initToConnectingState();
1172         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT);
1173         event.valueInt = StackEvent.CMD_RESULT_TYPE_OK;
1174         event.device = mTestDevice;
1175         sendMessage(StackEvent.STACK_EVENT, event);
1176         assertThat(mHeadsetClientStateMachine.getCurrentState())
1177                 .isInstanceOf(HeadsetClientStateMachine.Connecting.class);
1178     }
1179 
1180     @Test
testProcessStackEvent_Unknown_onConnectingState()1181     public void testProcessStackEvent_Unknown_onConnectingState() {
1182         String atCommand = "+ANDROID: (SINKAUDIOPOLICY)";
1183 
1184         initToConnectingState();
1185         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
1186         event.valueString = atCommand;
1187         event.device = mTestDevice;
1188         sendMessage(StackEvent.STACK_EVENT, event);
1189         assertThat(mHeadsetClientStateMachine.getCurrentState())
1190                 .isInstanceOf(HeadsetClientStateMachine.Connected.class);
1191         verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
1192     }
1193 
1194     @Test
testProcessConnectTimeoutMessage_onConnectingState()1195     public void testProcessConnectTimeoutMessage_onConnectingState() {
1196         initToConnectingState();
1197         sendMessageAndVerifyTransition(
1198                 mHeadsetClientStateMachine.obtainMessage(
1199                         HeadsetClientStateMachine.CONNECTING_TIMEOUT),
1200                 HeadsetClientStateMachine.Disconnected.class);
1201         verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(false));
1202     }
1203 
1204     @Test
testProcessStackEvent_inBandRingTone_onConnectingState()1205     public void testProcessStackEvent_inBandRingTone_onConnectingState() {
1206         initToConnectingState();
1207         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_IN_BAND_RINGTONE);
1208         event.valueInt = StackEvent.EVENT_TYPE_IN_BAND_RINGTONE;
1209         event.device = mTestDevice;
1210         assertThat(mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(StackEvent.STACK_EVENT))
1211                 .isFalse();
1212         sendMessage(StackEvent.STACK_EVENT, event);
1213         assertThat(mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(StackEvent.STACK_EVENT))
1214                 .isTrue();
1215     }
1216 
1217     @Test
testProcessConnectMessage_onConnectedState()1218     public void testProcessConnectMessage_onConnectedState() {
1219         initToConnectedState();
1220         sendMessage(HeadsetClientStateMachine.CONNECT);
1221         assertThat(mHeadsetClientStateMachine.getCurrentState())
1222                 .isInstanceOf(HeadsetClientStateMachine.Connected.class);
1223     }
1224 
1225     @Test
testProcessDisconnectMessage_onConnectedState()1226     public void testProcessDisconnectMessage_onConnectedState() {
1227         initToConnectedState();
1228         sendMessage(HeadsetClientStateMachine.DISCONNECT, mTestDevice);
1229         verify(mNativeInterface).disconnect(any(BluetoothDevice.class));
1230     }
1231 
1232     @Test
1233     @EnableFlags(Flags.FLAG_HFP_CLIENT_DISCONNECTING_STATE)
testConnectedState_ProcessDisconnectMessage_TransitionToDisconnecting()1234     public void testConnectedState_ProcessDisconnectMessage_TransitionToDisconnecting() {
1235         initToDisconnectingState();
1236         assertThat(mHeadsetClientStateMachine.getCurrentState())
1237                 .isInstanceOf(HeadsetClientStateMachine.Disconnecting.class);
1238     }
1239 
1240     @Test
1241     @EnableFlags(Flags.FLAG_HFP_CLIENT_DISCONNECTING_STATE)
testProcessStackEvent_ConnectionStateChanged_Disconnected_onConnectedState()1242     public void testProcessStackEvent_ConnectionStateChanged_Disconnected_onConnectedState() {
1243         initToConnectedState();
1244         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
1245         event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED;
1246         event.device = mTestDevice;
1247         sendMessage(StackEvent.STACK_EVENT, event);
1248         assertThat(mHeadsetClientStateMachine.getCurrentState())
1249                 .isInstanceOf(HeadsetClientStateMachine.Disconnected.class);
1250         verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(false));
1251     }
1252 
1253     @Test
testProcessConnectAudioMessage_onConnectedState()1254     public void testProcessConnectAudioMessage_onConnectedState() {
1255         initToConnectedState();
1256         sendMessage(HeadsetClientStateMachine.CONNECT_AUDIO);
1257         verify(mNativeInterface).connectAudio(any(BluetoothDevice.class));
1258     }
1259 
1260     @Test
testProcessDisconnectAudioMessage_onConnectedState()1261     public void testProcessDisconnectAudioMessage_onConnectedState() {
1262         initToConnectedState();
1263         sendMessage(HeadsetClientStateMachine.DISCONNECT_AUDIO);
1264         verify(mNativeInterface).disconnectAudio(any(BluetoothDevice.class));
1265     }
1266 
1267     @Test
testProcessVoiceRecognitionStartMessage_onConnectedState()1268     public void testProcessVoiceRecognitionStartMessage_onConnectedState() {
1269         initToConnectedState();
1270         sendMessage(HeadsetClientStateMachine.VOICE_RECOGNITION_START);
1271         verify(mNativeInterface).startVoiceRecognition(any(BluetoothDevice.class));
1272     }
1273 
1274     @Test
testProcessDisconnectMessage_onAudioOnState()1275     public void testProcessDisconnectMessage_onAudioOnState() {
1276         initToAudioOnState();
1277         assertThat(
1278                         mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
1279                                 HeadsetClientStateMachine.DISCONNECT))
1280                 .isFalse();
1281         sendMessage(HeadsetClientStateMachine.DISCONNECT, mTestDevice);
1282         assertThat(
1283                         mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
1284                                 HeadsetClientStateMachine.DISCONNECT))
1285                 .isTrue();
1286     }
1287 
1288     @Test
testProcessDisconnectAudioMessage_onAudioOnState()1289     public void testProcessDisconnectAudioMessage_onAudioOnState() {
1290         initToAudioOnState();
1291         sendMessage(HeadsetClientStateMachine.DISCONNECT_AUDIO);
1292         verify(mNativeInterface).disconnectAudio(any(BluetoothDevice.class));
1293     }
1294 
1295     @Test
testProcessHoldCall_onAudioOnState()1296     public void testProcessHoldCall_onAudioOnState() {
1297         initToAudioOnState();
1298         HfpClientCall call =
1299                 new HfpClientCall(
1300                         mTestDevice, 0, HfpClientCall.CALL_STATE_ACTIVE, "1", true, false, false);
1301         mHeadsetClientStateMachine.mCalls.put(0, call);
1302         int[] states = new int[1];
1303         states[0] = HfpClientCall.CALL_STATE_ACTIVE;
1304         sendMessage(HeadsetClientStateMachine.HOLD_CALL);
1305         verify(mNativeInterface).handleCallAction(any(BluetoothDevice.class), anyInt(), eq(0));
1306     }
1307 
1308     @Test
testProcessStackEvent_ConnectionStateChanged_onAudioOnState()1309     public void testProcessStackEvent_ConnectionStateChanged_onAudioOnState() {
1310         initToAudioOnState();
1311         assertThat(mHeadsetClientStateMachine.getCurrentState())
1312                 .isInstanceOf(HeadsetClientStateMachine.AudioOn.class);
1313         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
1314         event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED;
1315         event.device = mTestDevice;
1316         sendMessage(StackEvent.STACK_EVENT, event);
1317         assertThat(mHeadsetClientStateMachine.getCurrentState())
1318                 .isInstanceOf(HeadsetClientStateMachine.Disconnected.class);
1319         verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(false));
1320     }
1321 
1322     @Test
testProcessStackEvent_AudioStateChanged_onAudioOnState()1323     public void testProcessStackEvent_AudioStateChanged_onAudioOnState() {
1324         initToAudioOnState();
1325         assertThat(mHeadsetClientStateMachine.getCurrentState())
1326                 .isInstanceOf(HeadsetClientStateMachine.AudioOn.class);
1327         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
1328         event.valueInt = HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED;
1329         event.device = mTestDevice;
1330         sendMessage(StackEvent.STACK_EVENT, event);
1331         assertThat(mHeadsetClientStateMachine.getCurrentState())
1332                 .isInstanceOf(HeadsetClientStateMachine.Connected.class);
1333     }
1334 
1335     @Test
testProcessStackEvent_CodecSelection_onConnectedState()1336     public void testProcessStackEvent_CodecSelection_onConnectedState() {
1337         initToConnectedState();
1338         assertThat(mHeadsetClientStateMachine.getCurrentState())
1339                 .isInstanceOf(HeadsetClientStateMachine.Connected.class);
1340 
1341         // Trigger a MSBC codec stack event. Expect to mAudioWbs = true.
1342         mHeadsetClientStateMachine.mAudioWbs = false;
1343         mHeadsetClientStateMachine.mAudioSWB = false;
1344         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
1345         event.valueInt = HeadsetClientHalConstants.AUDIO_STATE_CONNECTED_MSBC;
1346         event.device = mTestDevice;
1347         sendMessage(StackEvent.STACK_EVENT, event);
1348         assertThat(mHeadsetClientStateMachine.mAudioWbs).isTrue();
1349         assertThat(mHeadsetClientStateMachine.mAudioSWB).isFalse();
1350 
1351         // Trigger a LC3 codec stack event. Expect to mAudioSWB = true.
1352         mHeadsetClientStateMachine.mAudioWbs = false;
1353         mHeadsetClientStateMachine.mAudioSWB = false;
1354         event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
1355         event.valueInt = HeadsetClientHalConstants.AUDIO_STATE_CONNECTED_LC3;
1356         event.device = mTestDevice;
1357         sendMessage(StackEvent.STACK_EVENT, event);
1358         assertThat(mHeadsetClientStateMachine.mAudioWbs).isFalse();
1359         assertThat(mHeadsetClientStateMachine.mAudioSWB).isTrue();
1360     }
1361 
1362     @Test
1363     @EnableFlags(Flags.FLAG_HFP_CLIENT_DISCONNECTING_STATE)
testDisconnectingState_TransitionToDisconnected()1364     public void testDisconnectingState_TransitionToDisconnected() {
1365         initToDisconnectingState();
1366 
1367         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
1368         event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED;
1369         event.device = mTestDevice;
1370 
1371         sendMessage(StackEvent.STACK_EVENT, event);
1372         mTestLooper.dispatchAll();
1373         verifySendBroadcastMultiplePermissions(hasExtra(EXTRA_STATE, STATE_DISCONNECTED));
1374         assertThat(mHeadsetClientStateMachine.getCurrentState())
1375                 .isInstanceOf(HeadsetClientStateMachine.Disconnected.class);
1376 
1377         verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(false));
1378     }
1379 
1380     @Test
1381     @EnableFlags(Flags.FLAG_HFP_CLIENT_DISCONNECTING_STATE)
testDisconnectingState_ReceiveConnectMsg_DeferMessage()1382     public void testDisconnectingState_ReceiveConnectMsg_DeferMessage() {
1383         // case CONNECT:
1384         initToDisconnectingState();
1385         assertThat(
1386                         mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
1387                                 HeadsetClientStateMachine.CONNECT))
1388                 .isFalse();
1389         sendMessage(HeadsetClientStateMachine.CONNECT);
1390         assertThat(
1391                         mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
1392                                 HeadsetClientStateMachine.CONNECT))
1393                 .isTrue();
1394         assertThat(mHeadsetClientStateMachine.getCurrentState())
1395                 .isInstanceOf(HeadsetClientStateMachine.Disconnecting.class);
1396     }
1397 
1398     @Test
1399     @EnableFlags(Flags.FLAG_HFP_CLIENT_DISCONNECTING_STATE)
testDisconnectingState_ReceiveConnectAudioMsg_DeferMessage()1400     public void testDisconnectingState_ReceiveConnectAudioMsg_DeferMessage() {
1401         // case CONNECT_AUDIO:
1402         initToDisconnectingState();
1403         assertThat(
1404                         mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
1405                                 HeadsetClientStateMachine.CONNECT_AUDIO))
1406                 .isFalse();
1407         sendMessage(HeadsetClientStateMachine.CONNECT_AUDIO);
1408         assertThat(
1409                         mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
1410                                 HeadsetClientStateMachine.CONNECT_AUDIO))
1411                 .isTrue();
1412         assertThat(mHeadsetClientStateMachine.getCurrentState())
1413                 .isInstanceOf(HeadsetClientStateMachine.Disconnecting.class);
1414     }
1415 
1416     @Test
1417     @EnableFlags(Flags.FLAG_HFP_CLIENT_DISCONNECTING_STATE)
testDisconnectingState_ReceiveDisconnectMsg_DeferMessage()1418     public void testDisconnectingState_ReceiveDisconnectMsg_DeferMessage() {
1419         // case DISCONNECT:
1420         initToDisconnectingState();
1421         sendMessage(HeadsetClientStateMachine.DISCONNECT);
1422         assertThat(
1423                         mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
1424                                 HeadsetClientStateMachine.DISCONNECT))
1425                 .isTrue();
1426         assertThat(mHeadsetClientStateMachine.getCurrentState())
1427                 .isInstanceOf(HeadsetClientStateMachine.Disconnecting.class);
1428     }
1429 
1430     @Test
1431     @EnableFlags(Flags.FLAG_HFP_CLIENT_DISCONNECTING_STATE)
testDisconnectingState_ReceiveDisconnectAudioMsg_DeferMessage()1432     public void testDisconnectingState_ReceiveDisconnectAudioMsg_DeferMessage() {
1433         // case DISCONNECT_AUDIO:
1434         initToDisconnectingState();
1435         assertThat(
1436                         mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
1437                                 HeadsetClientStateMachine.DISCONNECT_AUDIO))
1438                 .isFalse();
1439         sendMessage(HeadsetClientStateMachine.DISCONNECT_AUDIO);
1440         assertThat(
1441                         mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
1442                                 HeadsetClientStateMachine.DISCONNECT_AUDIO))
1443                 .isTrue();
1444         assertThat(mHeadsetClientStateMachine.getCurrentState())
1445                 .isInstanceOf(HeadsetClientStateMachine.Disconnecting.class);
1446     }
1447 
1448     @Test
1449     @EnableFlags(Flags.FLAG_HFP_CLIENT_DISCONNECTING_STATE)
testDisconnectingState_ReceiveUnknownMsg_NotHandled()1450     public void testDisconnectingState_ReceiveUnknownMsg_NotHandled() {
1451         initToDisconnectingState();
1452         sendMessage(HeadsetClientStateMachine.NO_ACTION);
1453         assertThat(mHeadsetClientStateMachine.getCurrentState())
1454                 .isInstanceOf(HeadsetClientStateMachine.Disconnecting.class);
1455     }
1456 
1457     @Test
1458     @EnableFlags(Flags.FLAG_HFP_CLIENT_DISCONNECTING_STATE)
testAudioOnState_ReceiveDisconnectMsg_DeferMessage()1459     public void testAudioOnState_ReceiveDisconnectMsg_DeferMessage() {
1460         initToAudioOnState();
1461         assertThat(
1462                         mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
1463                                 HeadsetClientStateMachine.DISCONNECT))
1464                 .isFalse();
1465         sendMessage(HeadsetClientStateMachine.DISCONNECT, mTestDevice);
1466         assertThat(
1467                         mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
1468                                 HeadsetClientStateMachine.DISCONNECT))
1469                 .isTrue();
1470         assertThat(mHeadsetClientStateMachine.getCurrentState())
1471                 .isInstanceOf(HeadsetClientStateMachine.AudioOn.class);
1472     }
1473 
1474     @Test
1475     @EnableFlags(Flags.FLAG_HFP_CLIENT_DISCONNECTING_STATE)
testDisconnectingState_DisconnectingTimeout_TransitionToDisconnected()1476     public void testDisconnectingState_DisconnectingTimeout_TransitionToDisconnected() {
1477         initToDisconnectingState();
1478         // Trigger timeout
1479         mTestLooper.moveTimeForward(HeadsetClientStateMachine.DISCONNECTING_TIMEOUT_MS);
1480         mTestLooper.dispatchAll();
1481         verifySendBroadcastMultiplePermissions(hasExtra(EXTRA_STATE, STATE_DISCONNECTED));
1482         assertThat(mHeadsetClientStateMachine.getCurrentState())
1483                 .isInstanceOf(HeadsetClientStateMachine.Disconnected.class);
1484 
1485         verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(false));
1486     }
1487 
1488     /**
1489      * Allow/disallow connection to any device
1490      *
1491      * @param allow if true, connection is allowed
1492      */
allowConnection(boolean allow)1493     private void allowConnection(boolean allow) {
1494         mHeadsetClientStateMachine.allowConnect = allow;
1495     }
1496 
initToConnectingState()1497     private void initToConnectingState() {
1498         doReturn(true).when(mNativeInterface).connect(any(BluetoothDevice.class));
1499         sendMessageAndVerifyTransition(
1500                 mHeadsetClientStateMachine.obtainMessage(
1501                         HeadsetClientStateMachine.CONNECT, mTestDevice),
1502                 HeadsetClientStateMachine.Connecting.class);
1503     }
1504 
initToConnectedState()1505     private void initToConnectedState() {
1506         String atCommand = "+ANDROID: (SINKAUDIOPOLICY)";
1507         initToConnectingState();
1508         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
1509         event.valueString = atCommand;
1510         event.device = mTestDevice;
1511         sendMessage(StackEvent.STACK_EVENT, event);
1512         assertThat(mHeadsetClientStateMachine.getCurrentState())
1513                 .isInstanceOf(HeadsetClientStateMachine.Connected.class);
1514         verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
1515     }
1516 
initToAudioOnState()1517     private void initToAudioOnState() {
1518         mHeadsetClientStateMachine.setAudioRouteAllowed(true);
1519         initToConnectedState();
1520         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
1521         event.valueInt = HeadsetClientHalConstants.AUDIO_STATE_CONNECTED;
1522         event.device = mTestDevice;
1523         sendMessage(StackEvent.STACK_EVENT, event);
1524         assertThat(mHeadsetClientStateMachine.getCurrentState())
1525                 .isInstanceOf(HeadsetClientStateMachine.AudioOn.class);
1526     }
1527 
initToDisconnectingState()1528     private void initToDisconnectingState() {
1529         initToConnectedState();
1530         sendMessageAndVerifyTransition(
1531                 mHeadsetClientStateMachine.obtainMessage(
1532                         HeadsetClientStateMachine.DISCONNECT, mTestDevice),
1533                 HeadsetClientStateMachine.Disconnecting.class);
1534     }
1535 
verifySendBroadcastMultiplePermissions(Matcher<Intent>.... matchers)1536     private void verifySendBroadcastMultiplePermissions(Matcher<Intent>... matchers) {
1537         mInOrder.verify(mHeadsetClientService)
1538                 .sendBroadcastMultiplePermissions(
1539                         MockitoHamcrest.argThat(AllOf.allOf(matchers)),
1540                         any(String[].class),
1541                         any(BroadcastOptions.class));
1542     }
1543 
verifySendBroadcast(Matcher<Intent>.... matchers)1544     private void verifySendBroadcast(Matcher<Intent>... matchers) {
1545         mInOrder.verify(mHeadsetClientService)
1546                 .sendBroadcast(
1547                         MockitoHamcrest.argThat(AllOf.allOf(matchers)),
1548                         anyString(),
1549                         any(Bundle.class));
1550     }
1551 
sendMessage(Message msg)1552     private void sendMessage(Message msg) {
1553         mHeadsetClientStateMachine.sendMessage(msg);
1554         mTestLooper.dispatchAll();
1555     }
1556 
sendMessage(int what)1557     private void sendMessage(int what) {
1558         sendMessage(mHeadsetClientStateMachine.obtainMessage(what));
1559     }
1560 
sendMessage(int what, Object obj)1561     private void sendMessage(int what, Object obj) {
1562         sendMessage(mHeadsetClientStateMachine.obtainMessage(what, obj));
1563     }
1564 
sendMessage(int what, int arg1, int arg2)1565     private void sendMessage(int what, int arg1, int arg2) {
1566         sendMessage(mHeadsetClientStateMachine.obtainMessage(what, arg1, arg2));
1567     }
1568 
sendMessage(int what, int arg1, int arg2, Object obj)1569     private void sendMessage(int what, int arg1, int arg2, Object obj) {
1570         sendMessage(mHeadsetClientStateMachine.obtainMessage(what, arg1, arg2, obj));
1571     }
1572 
sendMessageAndVerifyTransition(Message msg, Class<T> type)1573     private <T> void sendMessageAndVerifyTransition(Message msg, Class<T> type) {
1574         int previousState = mHeadsetClientStateMachine.getConnectionState(mTestDevice);
1575 
1576         sendMessage(msg);
1577 
1578         int newState = mHeadsetClientStateMachine.getConnectionState(mTestDevice);
1579         verifySendBroadcastMultiplePermissions(
1580                 hasExtra(EXTRA_PREVIOUS_STATE, previousState), hasExtra(EXTRA_STATE, newState));
1581 
1582         assertThat(mHeadsetClientStateMachine.getCurrentState()).isInstanceOf(type);
1583     }
1584 
1585     public static class TestHeadsetClientStateMachine extends HeadsetClientStateMachine {
1586 
1587         Boolean allowConnect = null;
1588         boolean mForceSetAudioPolicyProperty = false;
1589 
TestHeadsetClientStateMachine( AdapterService adapterService, HeadsetClientService context, HeadsetService headsetService, Looper looper, NativeInterface nativeInterface)1590         TestHeadsetClientStateMachine(
1591                 AdapterService adapterService,
1592                 HeadsetClientService context,
1593                 HeadsetService headsetService,
1594                 Looper looper,
1595                 NativeInterface nativeInterface) {
1596             super(adapterService, context, headsetService, looper, nativeInterface);
1597         }
1598 
doesSuperHaveDeferredMessages(int what)1599         public boolean doesSuperHaveDeferredMessages(int what) {
1600             return super.hasDeferredMessages(what);
1601         }
1602 
1603         @Override
okToConnect(BluetoothDevice device)1604         public boolean okToConnect(BluetoothDevice device) {
1605             return allowConnect != null ? allowConnect : super.okToConnect(device);
1606         }
1607 
1608         @Override
getConnectingTimePolicyProperty()1609         public int getConnectingTimePolicyProperty() {
1610             return 2;
1611         }
1612 
1613         @Override
getInBandRingtonePolicyProperty()1614         public int getInBandRingtonePolicyProperty() {
1615             return 1;
1616         }
1617 
setForceSetAudioPolicyProperty(boolean flag)1618         void setForceSetAudioPolicyProperty(boolean flag) {
1619             mForceSetAudioPolicyProperty = flag;
1620         }
1621 
1622         @Override
getForceSetAudioPolicyProperty()1623         boolean getForceSetAudioPolicyProperty() {
1624             return mForceSetAudioPolicyProperty;
1625         }
1626     }
1627 }
1628