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