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