CallAudioRouteStateMachineTest.java revision 0d5cae00ebfff7cf043a19ce8f833dde1d7e1282
1/* 2 * Copyright (C) 2015 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 17package com.android.server.telecom.tests; 18 19import android.content.Context; 20import android.media.AudioManager; 21import android.media.IAudioService; 22import android.telecom.CallAudioState; 23import android.test.suitebuilder.annotation.LargeTest; 24import android.test.suitebuilder.annotation.MediumTest; 25import android.test.suitebuilder.annotation.SmallTest; 26 27import com.android.server.telecom.BluetoothManager; 28import com.android.server.telecom.Call; 29import com.android.server.telecom.CallAudioModeStateMachine; 30import com.android.server.telecom.CallAudioRouteStateMachine; 31import com.android.server.telecom.CallsManager; 32import com.android.server.telecom.ConnectionServiceWrapper; 33import com.android.server.telecom.CallAudioManager; 34import com.android.server.telecom.StatusBarNotifier; 35import com.android.server.telecom.TelecomSystem; 36import com.android.server.telecom.WiredHeadsetManager; 37 38import org.mockito.ArgumentCaptor; 39import org.mockito.Mock; 40import org.mockito.MockitoAnnotations; 41import org.mockito.invocation.InvocationOnMock; 42import org.mockito.stubbing.Answer; 43 44import java.util.ArrayList; 45import java.util.List; 46 47import static org.mockito.Matchers.any; 48import static org.mockito.Matchers.same; 49import static org.mockito.Mockito.doAnswer; 50import static org.mockito.Mockito.doNothing; 51import static org.mockito.Mockito.never; 52import static org.mockito.Mockito.reset; 53import static org.mockito.Mockito.timeout; 54import static org.mockito.Mockito.verify; 55import static org.mockito.Mockito.when; 56 57 58public class CallAudioRouteStateMachineTest 59 extends StateMachineTestBase<CallAudioRouteStateMachine> { 60 private static final int NONE = 0; 61 private static final int ON = 1; 62 private static final int OFF = 2; 63 64 static class RoutingTestParameters extends TestParameters { 65 public String name; 66 public int initialRoute; 67 public int availableRoutes; // may excl. speakerphone, because that's always available 68 public int speakerInteraction; // one of NONE, ON, or OFF 69 public int bluetoothInteraction; // one of NONE, ON, or OFF 70 public int action; 71 public int expectedRoute; 72 public int expectedAvailableRoutes; // also may exclude the speakerphone. 73 public boolean doesDeviceSupportEarpiece; // set to false in the case of Wear devices 74 public boolean shouldRunWithFocus; 75 76 public RoutingTestParameters(String name, int initialRoute, int availableRoutes, int 77 speakerInteraction, int bluetoothInteraction, int action, int expectedRoute, int 78 expectedAvailableRoutes, boolean doesDeviceSupportEarpiece, 79 boolean shouldRunWithFocus) { 80 this.name = name; 81 this.initialRoute = initialRoute; 82 this.availableRoutes = availableRoutes; 83 this.speakerInteraction = speakerInteraction; 84 this.bluetoothInteraction = bluetoothInteraction; 85 this.action = action; 86 this.expectedRoute = expectedRoute; 87 this.expectedAvailableRoutes = expectedAvailableRoutes; 88 this.doesDeviceSupportEarpiece = doesDeviceSupportEarpiece; 89 this.shouldRunWithFocus = shouldRunWithFocus; 90 } 91 92 @Override 93 public String toString() { 94 return "RoutingTestParameters{" + 95 "name='" + name + '\'' + 96 ", initialRoute=" + initialRoute + 97 ", availableRoutes=" + availableRoutes + 98 ", speakerInteraction=" + speakerInteraction + 99 ", bluetoothInteraction=" + bluetoothInteraction + 100 ", action=" + action + 101 ", expectedRoute=" + expectedRoute + 102 ", expectedAvailableRoutes=" + expectedAvailableRoutes + 103 ", doesDeviceSupportEarpiece=" + doesDeviceSupportEarpiece + 104 ", shouldRunWithFocus=" + shouldRunWithFocus + 105 '}'; 106 } 107 } 108 109 @Mock CallsManager mockCallsManager; 110 @Mock BluetoothManager mockBluetoothManager; 111 @Mock IAudioService mockAudioService; 112 @Mock ConnectionServiceWrapper mockConnectionServiceWrapper; 113 @Mock WiredHeadsetManager mockWiredHeadsetManager; 114 @Mock StatusBarNotifier mockStatusBarNotifier; 115 @Mock Call fakeCall; 116 117 private CallAudioManager.AudioServiceFactory mAudioServiceFactory; 118 private static final int TEST_TIMEOUT = 500; 119 private AudioManager mockAudioManager; 120 private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { }; 121 122 @Override 123 public void setUp() throws Exception { 124 super.setUp(); 125 MockitoAnnotations.initMocks(this); 126 mContext = mComponentContextFixture.getTestDouble().getApplicationContext(); 127 mockAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 128 129 mAudioServiceFactory = new CallAudioManager.AudioServiceFactory() { 130 @Override 131 public IAudioService getAudioService() { 132 return mockAudioService; 133 } 134 }; 135 136 when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall); 137 when(mockCallsManager.getLock()).thenReturn(mLock); 138 when(fakeCall.getConnectionService()).thenReturn(mockConnectionServiceWrapper); 139 when(fakeCall.isAlive()).thenReturn(true); 140 doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class), 141 any(CallAudioState.class)); 142 } 143 144 @LargeTest 145 public void testStateMachineTransitionsWithFocus() throws Throwable { 146 List<RoutingTestParameters> paramList = generateTransitionTests(true); 147 parametrizedTestStateMachine(paramList); 148 } 149 150 @LargeTest 151 public void testStateMachineTransitionsWithoutFocus() throws Throwable { 152 List<RoutingTestParameters> paramList = generateTransitionTests(false); 153 parametrizedTestStateMachine(paramList); 154 } 155 156 @MediumTest 157 public void testSpeakerPersistence() { 158 CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( 159 mContext, 160 mockCallsManager, 161 mockBluetoothManager, 162 mockWiredHeadsetManager, 163 mockStatusBarNotifier, 164 mAudioServiceFactory, 165 true); 166 167 when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn(false); 168 when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(true); 169 when(mockAudioManager.isSpeakerphoneOn()).thenReturn(true); 170 doAnswer(new Answer() { 171 @Override 172 public Object answer(InvocationOnMock invocation) throws Throwable { 173 Object[] args = invocation.getArguments(); 174 when(mockAudioManager.isSpeakerphoneOn()).thenReturn((Boolean) args[0]); 175 return null; 176 } 177 }).when(mockAudioManager).setSpeakerphoneOn(any(Boolean.class)); 178 CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER, 179 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER); 180 stateMachine.initialize(initState); 181 182 stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS, 183 CallAudioRouteStateMachine.HAS_FOCUS); 184 stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET); 185 CallAudioState expectedMiddleState = new CallAudioState(false, 186 CallAudioState.ROUTE_WIRED_HEADSET, 187 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER); 188 verifyNewSystemCallAudioState(initState, expectedMiddleState); 189 resetMocks(); 190 191 stateMachine.sendMessageWithSessionInfo( 192 CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET); 193 verifyNewSystemCallAudioState(expectedMiddleState, initState); 194 } 195 196 @MediumTest 197 public void testUserBluetoothSwitchOff() { 198 CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( 199 mContext, 200 mockCallsManager, 201 mockBluetoothManager, 202 mockWiredHeadsetManager, 203 mockStatusBarNotifier, 204 mAudioServiceFactory, 205 true); 206 207 when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn(false); 208 when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(true); 209 when(mockAudioManager.isSpeakerphoneOn()).thenReturn(true); 210 CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, 211 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH); 212 stateMachine.initialize(initState); 213 214 stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS, 215 CallAudioRouteStateMachine.HAS_FOCUS); 216 stateMachine.sendMessageWithSessionInfo( 217 CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE); 218 CallAudioState expectedEndState = new CallAudioState(false, 219 CallAudioState.ROUTE_EARPIECE, 220 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH); 221 222 waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE); 223 verifyNewSystemCallAudioState(initState, expectedEndState); 224 resetMocks(); 225 226 stateMachine.sendMessageWithSessionInfo( 227 CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH); 228 stateMachine.sendMessageWithSessionInfo( 229 CallAudioRouteStateMachine.CONNECT_BLUETOOTH); 230 231 waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE); 232 233 assertEquals(expectedEndState, stateMachine.getCurrentCallAudioState()); 234 } 235 236 @SmallTest 237 public void testInitializationWithEarpieceNoHeadsetNoBluetooth() { 238 CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE, 239 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER); 240 initializationTestHelper(expectedState, true); 241 } 242 243 @SmallTest 244 public void testInitializationWithEarpieceAndHeadsetNoBluetooth() { 245 CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_WIRED_HEADSET, 246 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER); 247 initializationTestHelper(expectedState, true); 248 } 249 250 @SmallTest 251 public void testInitializationWithEarpieceAndHeadsetAndBluetooth() { 252 CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, 253 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER 254 | CallAudioState.ROUTE_BLUETOOTH); 255 initializationTestHelper(expectedState, true); 256 } 257 258 @SmallTest 259 public void testInitializationWithEarpieceAndBluetoothNoHeadset() { 260 CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, 261 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER 262 | CallAudioState.ROUTE_BLUETOOTH); 263 initializationTestHelper(expectedState, true); 264 } 265 266 @SmallTest 267 public void testInitializationWithNoEarpieceNoHeadsetNoBluetooth() { 268 CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER, 269 CallAudioState.ROUTE_SPEAKER); 270 initializationTestHelper(expectedState, false); 271 } 272 273 @SmallTest 274 public void testInitializationWithHeadsetNoBluetoothNoEarpiece() { 275 CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_WIRED_HEADSET, 276 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER); 277 initializationTestHelper(expectedState, false); 278 } 279 280 @SmallTest 281 public void testInitializationWithHeadsetAndBluetoothNoEarpiece() { 282 CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, 283 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER 284 | CallAudioState.ROUTE_BLUETOOTH); 285 initializationTestHelper(expectedState, false); 286 } 287 288 @SmallTest 289 public void testInitializationWithBluetoothNoHeadsetNoEarpiece() { 290 CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, 291 CallAudioState.ROUTE_SPEAKER | CallAudioState.ROUTE_BLUETOOTH); 292 initializationTestHelper(expectedState, false); 293 } 294 295 private void initializationTestHelper(CallAudioState expectedState, 296 boolean doesDeviceSupportEarpiece) { 297 when(mockWiredHeadsetManager.isPluggedIn()).thenReturn( 298 (expectedState.getSupportedRouteMask() & CallAudioState.ROUTE_WIRED_HEADSET) != 0); 299 when(mockBluetoothManager.isBluetoothAvailable()).thenReturn( 300 (expectedState.getSupportedRouteMask() & CallAudioState.ROUTE_BLUETOOTH) != 0); 301 302 CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( 303 mContext, 304 mockCallsManager, 305 mockBluetoothManager, 306 mockWiredHeadsetManager, 307 mockStatusBarNotifier, 308 mAudioServiceFactory, 309 doesDeviceSupportEarpiece); 310 stateMachine.initialize(); 311 assertEquals(expectedState, stateMachine.getCurrentCallAudioState()); 312 } 313 314 private List<RoutingTestParameters> generateTransitionTests(boolean shouldRunWithFocus) { 315 List<RoutingTestParameters> params = new ArrayList<>(); 316 params.add(new RoutingTestParameters( 317 "Connect headset during earpiece", // name 318 CallAudioState.ROUTE_EARPIECE, // initialRoute 319 CallAudioState.ROUTE_EARPIECE, // availableRoutes 320 NONE, // speakerInteraction 321 NONE, // bluetoothInteraction 322 CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action 323 CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute 324 CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes 325 true, // doesDeviceSupportEarpiece 326 shouldRunWithFocus 327 )); 328 329 params.add(new RoutingTestParameters( 330 "Connect headset during bluetooth", // name 331 CallAudioState.ROUTE_BLUETOOTH, // initialRoute 332 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes 333 NONE, // speakerInteraction 334 OFF, // bluetoothInteraction 335 CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action 336 CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute 337 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // expectedAvai 338 true, // doesDeviceSupportEarpiece 339 shouldRunWithFocus 340 )); 341 342 params.add(new RoutingTestParameters( 343 "Connect headset during speakerphone", // name 344 CallAudioState.ROUTE_SPEAKER, // initialRoute 345 CallAudioState.ROUTE_EARPIECE, // availableRoutes 346 OFF, // speakerInteraction 347 NONE, // bluetoothInteraction 348 CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action 349 CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute 350 CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes 351 true, // doesDeviceSupportEarpiece 352 shouldRunWithFocus 353 )); 354 355 params.add(new RoutingTestParameters( 356 "Disconnect headset during headset", // name 357 CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute 358 CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes 359 NONE, // speakerInteraction 360 NONE, // bluetoothInteraction 361 CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action 362 CallAudioState.ROUTE_EARPIECE, // expectedRoute 363 CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes 364 true, // doesDeviceSupportEarpiece 365 shouldRunWithFocus 366 )); 367 368 params.add(new RoutingTestParameters( 369 "Disconnect headset during headset with bluetooth available", // name 370 CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute 371 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou 372 NONE, // speakerInteraction 373 NONE, // bluetoothInteraction 374 CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action 375 CallAudioState.ROUTE_EARPIECE, // expectedRoute 376 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable 377 true, // doesDeviceSupportEarpiece 378 shouldRunWithFocus 379 )); 380 381 params.add(new RoutingTestParameters( 382 "Disconnect headset during bluetooth", // name 383 CallAudioState.ROUTE_BLUETOOTH, // initialRoute 384 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou 385 NONE, // speakerInteraction 386 NONE, // bluetoothInteraction 387 CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action 388 CallAudioState.ROUTE_BLUETOOTH, // expectedRoute 389 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable 390 true, // doesDeviceSupportEarpiece 391 shouldRunWithFocus 392 )); 393 394 params.add(new RoutingTestParameters( 395 "Disconnect headset during speakerphone", // name 396 CallAudioState.ROUTE_SPEAKER, // initialRoute 397 CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes 398 NONE, // speakerInteraction 399 NONE, // bluetoothInteraction 400 CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action 401 CallAudioState.ROUTE_SPEAKER, // expectedRoute 402 CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes 403 true, // doesDeviceSupportEarpiece 404 shouldRunWithFocus 405 )); 406 407 params.add(new RoutingTestParameters( 408 "Disconnect headset during speakerphone with bluetooth available", // name 409 CallAudioState.ROUTE_SPEAKER, // initialRoute 410 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou 411 NONE, // speakerInteraction 412 NONE, // bluetoothInteraction 413 CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action 414 CallAudioState.ROUTE_SPEAKER, // expectedRoute 415 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable 416 true, // doesDeviceSupportEarpiece 417 shouldRunWithFocus 418 )); 419 420 params.add(new RoutingTestParameters( 421 "Connect bluetooth during earpiece", // name 422 CallAudioState.ROUTE_EARPIECE, // initialRoute 423 CallAudioState.ROUTE_EARPIECE, // availableRoutes 424 NONE, // speakerInteraction 425 ON, // bluetoothInteraction 426 CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action 427 CallAudioState.ROUTE_BLUETOOTH, // expectedRoute 428 CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_EARPIECE, // expectedAvailable 429 true, // doesDeviceSupportEarpiece 430 shouldRunWithFocus 431 )); 432 433 params.add(new RoutingTestParameters( 434 "Connect bluetooth during wired headset", // name 435 CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute 436 CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes 437 NONE, // speakerInteraction 438 ON, // bluetoothInteraction 439 CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action 440 CallAudioState.ROUTE_BLUETOOTH, // expectedRoute 441 CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvai 442 true, // doesDeviceSupportEarpiece 443 shouldRunWithFocus 444 )); 445 446 params.add(new RoutingTestParameters( 447 "Connect bluetooth during speakerphone", // name 448 CallAudioState.ROUTE_SPEAKER, // initialRoute 449 CallAudioState.ROUTE_EARPIECE, // availableRoutes 450 OFF, // speakerInteraction 451 ON, // bluetoothInteraction 452 CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action 453 CallAudioState.ROUTE_BLUETOOTH, // expectedRoute 454 CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_EARPIECE, // expectedAvailable 455 true, // doesDeviceSupportEarpiece 456 shouldRunWithFocus 457 )); 458 459 params.add(new RoutingTestParameters( 460 "Disconnect bluetooth during bluetooth without headset in", // name 461 CallAudioState.ROUTE_BLUETOOTH, // initialRoute 462 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes 463 NONE, // speakerInteraction 464 OFF, // bluetoothInteraction 465 CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action 466 CallAudioState.ROUTE_EARPIECE, // expectedRoute 467 CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes 468 true, // doesDeviceSupportEarpiece 469 shouldRunWithFocus 470 )); 471 472 params.add(new RoutingTestParameters( 473 "Disconnect bluetooth during bluetooth with headset in", // name 474 CallAudioState.ROUTE_BLUETOOTH, // initialRoute 475 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou 476 NONE, // speakerInteraction 477 OFF, // bluetoothInteraction 478 CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action 479 CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute 480 CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes 481 true, // doesDeviceSupportEarpiece 482 shouldRunWithFocus 483 )); 484 485 params.add(new RoutingTestParameters( 486 "Disconnect bluetooth during speakerphone", // name 487 CallAudioState.ROUTE_SPEAKER, // initialRoute 488 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou 489 NONE, // speakerInteraction 490 NONE, // bluetoothInteraction 491 CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action 492 CallAudioState.ROUTE_SPEAKER, // expectedRoute 493 CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes 494 true, // doesDeviceSupportEarpiece 495 shouldRunWithFocus 496 )); 497 498 params.add(new RoutingTestParameters( 499 "Disconnect bluetooth during earpiece", // name 500 CallAudioState.ROUTE_EARPIECE, // initialRoute 501 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes 502 NONE, // speakerInteraction 503 NONE, // bluetoothInteraction 504 CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action 505 CallAudioState.ROUTE_EARPIECE, // expectedRoute 506 CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes 507 true, // doesDeviceSupportEarpiece 508 shouldRunWithFocus 509 )); 510 511 params.add(new RoutingTestParameters( 512 "Switch to speakerphone from earpiece", // name 513 CallAudioState.ROUTE_EARPIECE, // initialRoute 514 CallAudioState.ROUTE_EARPIECE, // availableRoutes 515 ON, // speakerInteraction 516 NONE, // bluetoothInteraction 517 CallAudioRouteStateMachine.SWITCH_SPEAKER, // action 518 CallAudioState.ROUTE_SPEAKER, // expectedRoute 519 CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes 520 true, // doesDeviceSupportEarpiece 521 shouldRunWithFocus 522 )); 523 524 params.add(new RoutingTestParameters( 525 "Switch to speakerphone from headset", // name 526 CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute 527 CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes 528 ON, // speakerInteraction 529 NONE, // bluetoothInteraction 530 CallAudioRouteStateMachine.SWITCH_SPEAKER, // action 531 CallAudioState.ROUTE_SPEAKER, // expectedRoute 532 CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes 533 true, // doesDeviceSupportEarpiece 534 shouldRunWithFocus 535 )); 536 537 params.add(new RoutingTestParameters( 538 "Switch to speakerphone from bluetooth", // name 539 CallAudioState.ROUTE_BLUETOOTH, // initialRoute 540 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou 541 ON, // speakerInteraction 542 OFF, // bluetoothInteraction 543 CallAudioRouteStateMachine.SWITCH_SPEAKER, // action 544 CallAudioState.ROUTE_SPEAKER, // expectedRoute 545 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // expectedAvai 546 true, // doesDeviceSupportEarpiece 547 shouldRunWithFocus 548 )); 549 550 params.add(new RoutingTestParameters( 551 "Switch to earpiece from bluetooth", // name 552 CallAudioState.ROUTE_BLUETOOTH, // initialRoute 553 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes 554 NONE, // speakerInteraction 555 OFF, // bluetoothInteraction 556 CallAudioRouteStateMachine.SWITCH_EARPIECE, // action 557 CallAudioState.ROUTE_EARPIECE, // expectedRoute 558 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable 559 true, // doesDeviceSupportEarpiece 560 shouldRunWithFocus 561 )); 562 563 params.add(new RoutingTestParameters( 564 "Switch to earpiece from speakerphone", // name 565 CallAudioState.ROUTE_SPEAKER, // initialRoute 566 CallAudioState.ROUTE_EARPIECE, // availableRoutes 567 OFF, // speakerInteraction 568 NONE, // bluetoothInteraction 569 CallAudioRouteStateMachine.SWITCH_EARPIECE, // action 570 CallAudioState.ROUTE_EARPIECE, // expectedRoute 571 CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes 572 true, // doesDeviceSupportEarpiece 573 shouldRunWithFocus 574 )); 575 576 params.add(new RoutingTestParameters( 577 "Switch to bluetooth from speakerphone", // name 578 CallAudioState.ROUTE_SPEAKER, // initialRoute 579 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes 580 OFF, // speakerInteraction 581 ON, // bluetoothInteraction 582 CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action 583 CallAudioState.ROUTE_BLUETOOTH, // expectedRoute 584 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable 585 true, // doesDeviceSupportEarpiece 586 shouldRunWithFocus 587 )); 588 589 params.add(new RoutingTestParameters( 590 "Switch to bluetooth from earpiece", // name 591 CallAudioState.ROUTE_EARPIECE, // initialRoute 592 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes 593 NONE, // speakerInteraction 594 ON, // bluetoothInteraction 595 CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action 596 CallAudioState.ROUTE_BLUETOOTH, // expectedRoute 597 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable 598 true, // doesDeviceSupportEarpiece 599 shouldRunWithFocus 600 )); 601 602 params.add(new RoutingTestParameters( 603 "Switch to bluetooth from wired headset", // name 604 CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute 605 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou 606 NONE, // speakerInteraction 607 ON, // bluetoothInteraction 608 CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action 609 CallAudioState.ROUTE_BLUETOOTH, // expectedRoute 610 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // expectedAvai 611 true, // doesDeviceSupportEarpiece 612 shouldRunWithFocus 613 )); 614 615 params.add(new RoutingTestParameters( 616 "Switch from bluetooth to wired/earpiece when neither are available", // name 617 CallAudioState.ROUTE_BLUETOOTH, // initialRoute 618 CallAudioState.ROUTE_BLUETOOTH, // availableRoutes 619 ON, // speakerInteraction 620 OFF, // bluetoothInteraction 621 CallAudioRouteStateMachine.SWITCH_BASELINE_ROUTE, // action 622 CallAudioState.ROUTE_SPEAKER, // expectedRoute 623 CallAudioState.ROUTE_BLUETOOTH, // expectedAvailableRoutes 624 false, // doesDeviceSupportEarpiece 625 shouldRunWithFocus 626 )); 627 628 params.add(new RoutingTestParameters( 629 "Disconnect wired headset when device does not support earpiece", // name 630 CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute 631 CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes 632 ON, // speakerInteraction 633 NONE, // bluetoothInteraction 634 CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action 635 CallAudioState.ROUTE_SPEAKER, // expectedRoute 636 CallAudioState.ROUTE_SPEAKER, // expectedAvailableRoutes 637 false, // doesDeviceSupportEarpiece 638 shouldRunWithFocus 639 )); 640 641 return params; 642 } 643 644 @Override 645 protected void runParametrizedTestCase(TestParameters _params) throws Throwable { 646 RoutingTestParameters params = (RoutingTestParameters) _params; 647 if (params.shouldRunWithFocus) { 648 runParametrizedTestCaseWithFocus(params); 649 } else { 650 runParametrizedTestCaseWithoutFocus(params); 651 } 652 } 653 654 private void runParametrizedTestCaseWithFocus(final RoutingTestParameters params) 655 throws Throwable { 656 resetMocks(); 657 658 // Construct a fresh state machine on every case 659 final CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( 660 mContext, 661 mockCallsManager, 662 mockBluetoothManager, 663 mockWiredHeadsetManager, 664 mockStatusBarNotifier, 665 mAudioServiceFactory, 666 params.doesDeviceSupportEarpiece); 667 668 // Set up bluetooth and speakerphone state 669 when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn( 670 params.initialRoute == CallAudioState.ROUTE_BLUETOOTH); 671 when(mockBluetoothManager.isBluetoothAvailable()).thenReturn( 672 (params.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0 673 || (params.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0); 674 when(mockAudioManager.isSpeakerphoneOn()).thenReturn( 675 params.initialRoute == CallAudioState.ROUTE_SPEAKER); 676 677 // Set the initial CallAudioState object 678 final CallAudioState initState = new CallAudioState(false, 679 params.initialRoute, (params.availableRoutes | CallAudioState.ROUTE_SPEAKER)); 680 stateMachine.initialize(initState); 681 // Make the state machine have focus so that we actually do something 682 stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS, 683 CallAudioRouteStateMachine.HAS_FOCUS); 684 stateMachine.sendMessageWithSessionInfo(params.action); 685 686 waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE); 687 688 stateMachine.quitStateMachine(); 689 690 // Verify interactions with the speakerphone and bluetooth systems 691 switch (params.bluetoothInteraction) { 692 case NONE: 693 verify(mockBluetoothManager, never()).disconnectBluetoothAudio(); 694 verify(mockBluetoothManager, never()).connectBluetoothAudio(); 695 break; 696 case ON: 697 verify(mockBluetoothManager).connectBluetoothAudio(); 698 699 verify(mockBluetoothManager, never()).disconnectBluetoothAudio(); 700 break; 701 case OFF: 702 verify(mockBluetoothManager, never()).connectBluetoothAudio(); 703 verify(mockBluetoothManager).disconnectBluetoothAudio(); 704 } 705 706 switch (params.speakerInteraction) { 707 case NONE: 708 verify(mockAudioManager, never()).setSpeakerphoneOn(any(Boolean.class)); 709 break; 710 case ON: // fall through 711 case OFF: 712 verify(mockAudioManager).setSpeakerphoneOn(params.speakerInteraction == ON); 713 } 714 715 // Verify the end state 716 CallAudioState expectedState = new CallAudioState(false, params.expectedRoute, 717 params.expectedAvailableRoutes | CallAudioState.ROUTE_SPEAKER); 718 verifyNewSystemCallAudioState(initState, expectedState); 719 } 720 721 private void runParametrizedTestCaseWithoutFocus(final RoutingTestParameters params) 722 throws Throwable { 723 resetMocks(); 724 725 // Construct a fresh state machine on every case 726 final CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( 727 mContext, 728 mockCallsManager, 729 mockBluetoothManager, 730 mockWiredHeadsetManager, 731 mockStatusBarNotifier, 732 mAudioServiceFactory, 733 params.doesDeviceSupportEarpiece); 734 735 // Set up bluetooth and speakerphone state 736 when(mockBluetoothManager.isBluetoothAvailable()).thenReturn( 737 (params.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0 738 || (params.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0); 739 when(mockAudioManager.isSpeakerphoneOn()).thenReturn( 740 params.initialRoute == CallAudioState.ROUTE_SPEAKER); 741 742 // Set the initial CallAudioState object 743 CallAudioState initState = new CallAudioState(false, 744 params.initialRoute, (params.availableRoutes | CallAudioState.ROUTE_SPEAKER)); 745 stateMachine.initialize(initState); 746 // Omit the focus-getting statement 747 stateMachine.sendMessageWithSessionInfo(params.action); 748 749 waitForStateMachineActionCompletion(stateMachine, CallAudioModeStateMachine.RUN_RUNNABLE); 750 751 stateMachine.quitStateMachine(); 752 753 // Verify that no substantive interactions have taken place with the 754 // rest of the system 755 verifyNoSystemAudioChanges(); 756 757 // Verify the end state 758 CallAudioState expectedState = new CallAudioState(false, params.expectedRoute, 759 params.expectedAvailableRoutes | CallAudioState.ROUTE_SPEAKER); 760 assertEquals(expectedState, stateMachine.getCurrentCallAudioState()); 761 } 762 763 private void verifyNoSystemAudioChanges() { 764 verify(mockBluetoothManager, never()).disconnectBluetoothAudio(); 765 verify(mockBluetoothManager, never()).connectBluetoothAudio(); 766 verify(mockAudioManager, never()).setSpeakerphoneOn(any(Boolean.class)); 767 verify(mockCallsManager, never()).onCallAudioStateChanged(any(CallAudioState.class), 768 any(CallAudioState.class)); 769 verify(mockConnectionServiceWrapper, never()).onCallAudioStateChanged( 770 any(Call.class), any(CallAudioState.class)); 771 } 772 773 private void verifyNewSystemCallAudioState(CallAudioState expectedOldState, 774 CallAudioState expectedNewState) { 775 ArgumentCaptor<CallAudioState> oldStateCaptor = ArgumentCaptor.forClass( 776 CallAudioState.class); 777 ArgumentCaptor<CallAudioState> newStateCaptor1 = ArgumentCaptor.forClass( 778 CallAudioState.class); 779 ArgumentCaptor<CallAudioState> newStateCaptor2 = ArgumentCaptor.forClass( 780 CallAudioState.class); 781 verify(mockCallsManager, timeout(TEST_TIMEOUT).atLeastOnce()).onCallAudioStateChanged( 782 oldStateCaptor.capture(), newStateCaptor1.capture()); 783 verify(mockConnectionServiceWrapper, timeout(TEST_TIMEOUT).atLeastOnce()) 784 .onCallAudioStateChanged(same(fakeCall), newStateCaptor2.capture()); 785 786 assertTrue(oldStateCaptor.getValue().equals(expectedOldState)); 787 assertTrue(newStateCaptor1.getValue().equals(expectedNewState)); 788 assertTrue(newStateCaptor2.getValue().equals(expectedNewState)); 789 } 790 791 private void resetMocks() { 792 reset(mockAudioManager, mockBluetoothManager, mockCallsManager, 793 mockConnectionServiceWrapper); 794 when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall); 795 when(mockCallsManager.getLock()).thenReturn(mLock); 796 doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class), 797 any(CallAudioState.class)); 798 } 799} 800