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