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