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