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