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 }