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