• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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