• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.bluetooth.hfpclient;
2 
3 import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.AT_OK;
4 
5 import static org.mockito.Mockito.*;
6 
7 import android.bluetooth.BluetoothAdapter;
8 import android.bluetooth.BluetoothDevice;
9 import android.bluetooth.BluetoothHeadsetClient;
10 import android.bluetooth.BluetoothHeadsetClientCall;
11 import android.bluetooth.BluetoothProfile;
12 import android.content.Context;
13 import android.content.Intent;
14 import android.content.res.Resources;
15 import android.media.AudioManager;
16 import android.os.HandlerThread;
17 
18 import androidx.test.InstrumentationRegistry;
19 import androidx.test.espresso.intent.matcher.IntentMatchers;
20 import androidx.test.filters.LargeTest;
21 import androidx.test.filters.MediumTest;
22 import androidx.test.filters.SmallTest;
23 import androidx.test.runner.AndroidJUnit4;
24 
25 import com.android.bluetooth.R;
26 
27 import org.hamcrest.core.AllOf;
28 import org.hamcrest.core.IsInstanceOf;
29 import org.junit.After;
30 import org.junit.Assert;
31 import org.junit.Assume;
32 import org.junit.Before;
33 import org.junit.Test;
34 import org.junit.runner.RunWith;
35 import org.mockito.ArgumentCaptor;
36 import org.mockito.Mock;
37 import org.mockito.MockitoAnnotations;
38 import org.mockito.hamcrest.MockitoHamcrest;
39 
40 @LargeTest
41 @RunWith(AndroidJUnit4.class)
42 public class HeadsetClientStateMachineTest {
43     private BluetoothAdapter mAdapter;
44     private HandlerThread mHandlerThread;
45     private HeadsetClientStateMachine mHeadsetClientStateMachine;
46     private BluetoothDevice mTestDevice;
47     private Context mTargetContext;
48 
49     @Mock
50     private Resources mMockHfpResources;
51     @Mock
52     private HeadsetClientService mHeadsetClientService;
53     @Mock
54     private AudioManager mAudioManager;
55 
56     private static final int STANDARD_WAIT_MILLIS = 1000;
57     private static final int QUERY_CURRENT_CALLS_WAIT_MILLIS = 2000;
58     private static final int QUERY_CURRENT_CALLS_TEST_WAIT_MILLIS = QUERY_CURRENT_CALLS_WAIT_MILLIS
59             * 3 / 2;
60 
61     @Before
setUp()62     public void setUp() {
63         mTargetContext = InstrumentationRegistry.getTargetContext();
64         Assume.assumeTrue("Ignore test when HeadsetClientService is not enabled",
65                 mTargetContext.getResources().getBoolean(R.bool.profile_supported_hfpclient));
66         // Setup mocks and test assets
67         MockitoAnnotations.initMocks(this);
68         // Set a valid volume
69         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(2);
70         when(mAudioManager.getStreamMaxVolume(anyInt())).thenReturn(10);
71         when(mAudioManager.getStreamMinVolume(anyInt())).thenReturn(1);
72         when(mHeadsetClientService.getAudioManager()).thenReturn(
73                 mAudioManager);
74         when(mHeadsetClientService.getResources()).thenReturn(mMockHfpResources);
75         when(mMockHfpResources.getBoolean(R.bool.hfp_clcc_poll_during_call)).thenReturn(true);
76 
77         // This line must be called to make sure relevant objects are initialized properly
78         mAdapter = BluetoothAdapter.getDefaultAdapter();
79         // Get a device for testing
80         mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
81 
82         // Setup thread and looper
83         mHandlerThread = new HandlerThread("HeadsetClientStateMachineTestHandlerThread");
84         mHandlerThread.start();
85         // Manage looper execution in main test thread explicitly to guarantee timing consistency
86         mHeadsetClientStateMachine =
87                 new HeadsetClientStateMachine(mHeadsetClientService, mHandlerThread.getLooper());
88         mHeadsetClientStateMachine.start();
89     }
90 
91     @After
tearDown()92     public void tearDown() {
93         if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hfpclient)) {
94             return;
95         }
96         mHeadsetClientStateMachine.doQuit();
97         mHandlerThread.quit();
98     }
99 
100     /**
101      * Test that default state is disconnected
102      */
103     @SmallTest
104     @Test
testDefaultDisconnectedState()105     public void testDefaultDisconnectedState() {
106         Assert.assertEquals(mHeadsetClientStateMachine.getConnectionState(null),
107                 BluetoothProfile.STATE_DISCONNECTED);
108     }
109 
110     /**
111      * Test that an incoming connection with low priority is rejected
112      */
113     @MediumTest
114     @Test
testIncomingPriorityReject()115     public void testIncomingPriorityReject() {
116         // Return false for priority.
117         when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
118                 BluetoothProfile.PRIORITY_OFF);
119 
120         // Inject an event for when incoming connection is requested
121         StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
122         connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
123         connStCh.device = mTestDevice;
124         mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, connStCh);
125 
126         // Verify that only DISCONNECTED -> DISCONNECTED broadcast is fired
127         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS)).sendBroadcast(MockitoHamcrest
128                 .argThat(
129                 AllOf.allOf(IntentMatchers.hasAction(
130                         BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED),
131                         IntentMatchers.hasExtra(BluetoothProfile.EXTRA_STATE,
132                                 BluetoothProfile.STATE_DISCONNECTED),
133                         IntentMatchers.hasExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
134                                 BluetoothProfile.STATE_DISCONNECTED))), anyString());
135         // Check we are in disconnected state still.
136         Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
137                 IsInstanceOf.instanceOf(HeadsetClientStateMachine.Disconnected.class));
138     }
139 
140     /**
141      * Test that an incoming connection with high priority is accepted
142      */
143     @MediumTest
144     @Test
testIncomingPriorityAccept()145     public void testIncomingPriorityAccept() {
146         // Return true for priority.
147         when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
148                 BluetoothProfile.PRIORITY_ON);
149 
150         // Inject an event for when incoming connection is requested
151         StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
152         connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
153         connStCh.device = mTestDevice;
154         mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, connStCh);
155 
156         // Verify that one connection state broadcast is executed
157         ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
158         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS)).sendBroadcast(intentArgument1
159                 .capture(),
160                 anyString());
161         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
162                 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
163 
164         // Check we are in connecting state now.
165         Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
166                 IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connecting.class));
167 
168         // Send a message to trigger SLC connection
169         StackEvent slcEvent = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
170         slcEvent.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED;
171         slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS;
172         slcEvent.device = mTestDevice;
173         mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, slcEvent);
174 
175         // Verify that one connection state broadcast is executed
176         ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
177         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(2)).sendBroadcast(
178                 intentArgument2.capture(), anyString());
179         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
180                 intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
181         // Check we are in connecting state now.
182         Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
183                 IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
184     }
185 
186     /**
187      * Test that an incoming connection that times out
188      */
189     @MediumTest
190     @Test
testIncomingTimeout()191     public void testIncomingTimeout() {
192         // Return true for priority.
193         when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
194                 BluetoothProfile.PRIORITY_ON);
195 
196         // Inject an event for when incoming connection is requested
197         StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
198         connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
199         connStCh.device = mTestDevice;
200         mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, connStCh);
201 
202         // Verify that one connection state broadcast is executed
203         ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
204         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS)).sendBroadcast(intentArgument1
205                 .capture(),
206                 anyString());
207         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
208                 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
209 
210         // Check we are in connecting state now.
211         Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
212                 IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connecting.class));
213 
214         // Verify that one connection state broadcast is executed
215         ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
216         verify(mHeadsetClientService,
217                 timeout(HeadsetClientStateMachine.CONNECTING_TIMEOUT_MS * 2).times(
218                         2)).sendBroadcast(intentArgument2.capture(), anyString());
219         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
220                 intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
221 
222         // Check we are in connecting state now.
223         Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
224                 IsInstanceOf.instanceOf(HeadsetClientStateMachine.Disconnected.class));
225     }
226 
227     /**
228      * Test that In Band Ringtone information is relayed from phone.
229      */
230     @LargeTest
231     @Test
testInBandRingtone()232     public void testInBandRingtone() {
233         // Return true for priority.
234         when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
235                 BluetoothProfile.PRIORITY_ON);
236 
237         Assert.assertEquals(false, mHeadsetClientStateMachine.getInBandRing());
238 
239         // Inject an event for when incoming connection is requested
240         StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
241         connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
242         connStCh.device = mTestDevice;
243         mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, connStCh);
244 
245         // Verify that one connection state broadcast is executed
246         ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
247         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS)).sendBroadcast(intentArgument
248                 .capture(),
249                 anyString());
250         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
251                 intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
252 
253         // Send a message to trigger SLC connection
254         StackEvent slcEvent = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
255         slcEvent.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED;
256         slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS;
257         slcEvent.device = mTestDevice;
258         mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, slcEvent);
259 
260         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(2)).sendBroadcast(
261                 intentArgument.capture(),
262                 anyString());
263 
264         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
265                 intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
266 
267         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_IN_BAND_RINGTONE);
268         event.valueInt = 0;
269         event.device = mTestDevice;
270 
271         // Enable In Band Ring and verify state gets propagated.
272         StackEvent eventInBandRing = new StackEvent(StackEvent.EVENT_TYPE_IN_BAND_RINGTONE);
273         eventInBandRing.valueInt = 1;
274         eventInBandRing.device = mTestDevice;
275         mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventInBandRing);
276         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(3)).sendBroadcast(
277                 intentArgument.capture(),
278                 anyString());
279         Assert.assertEquals(1,
280                 intentArgument.getValue().getIntExtra(BluetoothHeadsetClient.EXTRA_IN_BAND_RING,
281                         -1));
282         Assert.assertEquals(true, mHeadsetClientStateMachine.getInBandRing());
283 
284 
285         // Simulate a new incoming phone call
286         StackEvent eventCallStatusUpdated = new StackEvent(StackEvent.EVENT_TYPE_CLIP);
287         mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventCallStatusUpdated);
288         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(3)).sendBroadcast(
289                 intentArgument.capture(),
290                 anyString());
291 
292         // Provide information about the new call
293         StackEvent eventIncomingCall = new StackEvent(StackEvent.EVENT_TYPE_CURRENT_CALLS);
294         eventIncomingCall.valueInt = 1; //index
295         eventIncomingCall.valueInt2 = 1; //direction
296         eventIncomingCall.valueInt3 = 4; //state
297         eventIncomingCall.valueInt4 = 0; //multi party
298         eventIncomingCall.valueString = "5551212"; //phone number
299         eventIncomingCall.device = mTestDevice;
300 
301         mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventIncomingCall);
302         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(3)).sendBroadcast(
303                 intentArgument.capture(),
304                 anyString());
305 
306 
307         // Signal that the complete list of calls was received.
308         StackEvent eventCommandStatus = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT);
309         eventCommandStatus.valueInt = AT_OK;
310         mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventCommandStatus);
311         verify(mHeadsetClientService, timeout(QUERY_CURRENT_CALLS_TEST_WAIT_MILLIS).times(4))
312                 .sendBroadcast(
313                 intentArgument.capture(),
314                 anyString());
315 
316         // Verify that the new call is being registered with the inBandRing flag set.
317         Assert.assertEquals(true,
318                 ((BluetoothHeadsetClientCall) intentArgument.getValue().getParcelableExtra(
319                         BluetoothHeadsetClient.EXTRA_CALL)).isInBandRing());
320 
321         // Disable In Band Ring and verify state gets propagated.
322         eventInBandRing.valueInt = 0;
323         mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventInBandRing);
324         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(5)).sendBroadcast(
325                 intentArgument.capture(),
326                 anyString());
327         Assert.assertEquals(0,
328                 intentArgument.getValue().getIntExtra(BluetoothHeadsetClient.EXTRA_IN_BAND_RING,
329                         -1));
330         Assert.assertEquals(false, mHeadsetClientStateMachine.getInBandRing());
331 
332     }
333 }
334