CallAudioRouteStateMachineTest.java revision f62630a57de0d52be2bdbc92a9bf8f305cc0892d
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;
23
24import com.android.server.telecom.BluetoothManager;
25import com.android.server.telecom.Call;
26import com.android.server.telecom.CallAudioManager;
27import com.android.server.telecom.CallAudioRouteStateMachine;
28import com.android.server.telecom.CallsManager;
29import com.android.server.telecom.ConnectionServiceWrapper;
30import com.android.server.telecom.StatusBarNotifier;
31import com.android.server.telecom.WiredHeadsetManager;
32
33import org.mockito.ArgumentCaptor;
34import org.mockito.Mock;
35import org.mockito.MockitoAnnotations;
36import org.mockito.invocation.InvocationOnMock;
37import org.mockito.stubbing.Answer;
38
39import java.util.ArrayList;
40import java.util.List;
41
42import static org.mockito.Matchers.any;
43import static org.mockito.Matchers.same;
44import static org.mockito.Mockito.doAnswer;
45import static org.mockito.Mockito.doNothing;
46import static org.mockito.Mockito.never;
47import static org.mockito.Mockito.reset;
48import static org.mockito.Mockito.timeout;
49import static org.mockito.Mockito.verify;
50import static org.mockito.Mockito.when;
51
52
53public class CallAudioRouteStateMachineTest extends TelecomTestCase {
54    private static final int NONE = 0;
55    private static final int ON = 1;
56    private static final int OFF = 2;
57
58    private class TestParameters {
59        public String name;
60        public int initialRoute;
61        public int availableRoutes; // may excl. speakerphone, because that's always available
62        public int speakerInteraction; // one of NONE, ON, or OFF
63        public int bluetoothInteraction; // one of NONE, ON, or OFF
64        public int action;
65        public int expectedRoute;
66        public int expectedAvailableRoutes; // also may exclude the speakerphone.
67
68        public TestParameters(String name, int initialRoute, int availableRoutes, int
69                speakerInteraction, int bluetoothInteraction, int action, int expectedRoute, int
70                expectedAvailableRoutes) {
71            this.name = name;
72            this.initialRoute = initialRoute;
73            this.availableRoutes = availableRoutes;
74            this.speakerInteraction = speakerInteraction;
75            this.bluetoothInteraction = bluetoothInteraction;
76            this.action = action;
77            this.expectedRoute = expectedRoute;
78            this.expectedAvailableRoutes = expectedAvailableRoutes;
79        }
80
81        @Override
82        public String toString() {
83            return "TestParameters{" +
84                    "name='" + name + '\'' +
85                    ", initialRoute=" + initialRoute +
86                    ", availableRoutes=" + availableRoutes +
87                    ", speakerInteraction=" + speakerInteraction +
88                    ", bluetoothInteraction=" + bluetoothInteraction +
89                    ", action=" + action +
90                    ", expectedRoute=" + expectedRoute +
91                    ", expectedAvailableRoutes=" + expectedAvailableRoutes +
92                    '}';
93        }
94    }
95
96    @Mock CallsManager mockCallsManager;
97    @Mock BluetoothManager mockBluetoothManager;
98    @Mock IAudioService mockAudioService;
99    @Mock ConnectionServiceWrapper mockConnectionServiceWrapper;
100    @Mock WiredHeadsetManager mockWiredHeadsetManager;
101    @Mock StatusBarNotifier mockStatusBarNotifier;
102    @Mock Call fakeCall;
103
104    private CallAudioManager.AudioServiceFactory mAudioServiceFactory;
105    private static final int TEST_TIMEOUT = 200;
106    private AudioManager mockAudioManager;
107    @Override
108    public void setUp() throws Exception {
109        super.setUp();
110        MockitoAnnotations.initMocks(this);
111        mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
112        mockAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
113
114        mAudioServiceFactory = new CallAudioManager.AudioServiceFactory() {
115            @Override
116            public IAudioService getAudioService() {
117                return mockAudioService;
118            }
119        };
120
121        when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall);
122        when(fakeCall.getConnectionService()).thenReturn(mockConnectionServiceWrapper);
123        when(fakeCall.isAlive()).thenReturn(true);
124        doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class),
125                any(CallAudioState.class));
126    }
127
128    public void testStateMachineTransitionsWithFocus() throws Throwable {
129        List<TestParameters> paramList = generateTransitionTests();
130        for (TestParameters params : paramList) {
131            try {
132                runParametrizedTestCaseWithFocus(params);
133            } catch (Throwable e) {
134                String newMessage = "Failed at parameters: \n" + params.toString() + '\n'
135                        + e.getMessage();
136                throw(new Throwable(newMessage, e));
137            }
138        }
139    }
140
141    public void testStateMachineTransitionsWithoutFocus() throws Throwable {
142        List<TestParameters> paramList = generateTransitionTests();
143        for (TestParameters params : paramList) {
144            try {
145                runParametrizedTestCaseWithoutFocus(params);
146            } catch (Throwable e) {
147                String newMessage = "Failed at parameters: \n" + params.toString() + '\n'
148                        + e.getMessage();
149                throw(new Throwable(newMessage, e));
150            }
151        }
152    }
153
154    public void testSpeakerPersistence() {
155        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
156                mContext,
157                mockCallsManager,
158                mockBluetoothManager,
159                mockWiredHeadsetManager,
160                mockStatusBarNotifier,
161                mAudioServiceFactory);
162
163        when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
164        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(true);
165        when(mockAudioManager.isSpeakerphoneOn()).thenReturn(true);
166        doAnswer(new Answer() {
167            @Override
168            public Object answer(InvocationOnMock invocation) throws Throwable {
169                Object[] args = invocation.getArguments();
170                when(mockAudioManager.isSpeakerphoneOn()).thenReturn((Boolean) args[0]);
171                return null;
172            }
173        }).when(mockAudioManager).setSpeakerphoneOn(any(Boolean.class));
174        CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
175                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
176        stateMachine.initialize(initState);
177
178        stateMachine.sendMessage(CallAudioRouteStateMachine.SWITCH_FOCUS,
179                CallAudioRouteStateMachine.HAS_FOCUS);
180        stateMachine.sendMessage(CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET);
181        CallAudioState expectedMiddleState = new CallAudioState(false,
182                CallAudioState.ROUTE_WIRED_HEADSET,
183                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER);
184        verifyNewSystemCallAudioState(initState, expectedMiddleState);
185        resetMocks();
186
187        stateMachine.sendMessage(CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET);
188        verifyNewSystemCallAudioState(expectedMiddleState, initState);
189    }
190
191    public void testInitializationWithNoHeadsetNoBluetooth() {
192        when(mockWiredHeadsetManager.isPluggedIn()).thenReturn(false);
193        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(false);
194
195        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
196                mContext,
197                mockCallsManager,
198                mockBluetoothManager,
199                mockWiredHeadsetManager,
200                mockStatusBarNotifier,
201                mAudioServiceFactory);
202        stateMachine.initialize();
203        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
204                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
205        assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
206    }
207
208    public void testInitializationWithHeadsetNoBluetooth() {
209        when(mockWiredHeadsetManager.isPluggedIn()).thenReturn(true);
210        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(false);
211
212        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
213                mContext,
214                mockCallsManager,
215                mockBluetoothManager,
216                mockWiredHeadsetManager,
217                mockStatusBarNotifier,
218                mAudioServiceFactory);
219        stateMachine.initialize();
220        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_WIRED_HEADSET,
221                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER);
222        assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
223    }
224
225    public void testInitializationWithHeadsetAndBluetooth() {
226        when(mockWiredHeadsetManager.isPluggedIn()).thenReturn(true);
227        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(true);
228
229        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
230                mContext,
231                mockCallsManager,
232                mockBluetoothManager,
233                mockWiredHeadsetManager,
234                mockStatusBarNotifier,
235                mAudioServiceFactory);
236        stateMachine.initialize();
237        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
238                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER
239                | CallAudioState.ROUTE_BLUETOOTH);
240        assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
241    }
242
243    public void testInitializationWithBluetoothNoHeadset() {
244        when(mockWiredHeadsetManager.isPluggedIn()).thenReturn(false);
245        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(true);
246
247        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
248                mContext,
249                mockCallsManager,
250                mockBluetoothManager,
251                mockWiredHeadsetManager,
252                mockStatusBarNotifier,
253                mAudioServiceFactory);
254        stateMachine.initialize();
255        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
256                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
257                        | CallAudioState.ROUTE_BLUETOOTH);
258        assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
259    }
260
261    private List<TestParameters> generateTransitionTests() {
262        List<TestParameters> params = new ArrayList<>();
263        params.add(new TestParameters(
264                "Connect headset during earpiece", // name
265                CallAudioState.ROUTE_EARPIECE, // initialRoute
266                CallAudioState.ROUTE_EARPIECE, // availableRoutes
267                NONE, // speakerInteraction
268                NONE, // bluetoothInteraction
269                CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action
270                CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
271                CallAudioState.ROUTE_WIRED_HEADSET // expectedAvailableRoutes
272        ));
273
274        params.add(new TestParameters(
275                "Connect headset during bluetooth", // name
276                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
277                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
278                NONE, // speakerInteraction
279                OFF, // bluetoothInteraction
280                CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action
281                CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
282                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH // expectedAvail
283        ));
284
285        params.add(new TestParameters(
286                "Connect headset during speakerphone", // name
287                CallAudioState.ROUTE_SPEAKER, // initialRoute
288                CallAudioState.ROUTE_EARPIECE, // availableRoutes
289                OFF, // speakerInteraction
290                NONE, // bluetoothInteraction
291                CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action
292                CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
293                CallAudioState.ROUTE_WIRED_HEADSET // expectedAvailableRoutes
294        ));
295
296        params.add(new TestParameters(
297                "Disconnect headset during headset", // name
298                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
299                CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
300                NONE, // speakerInteraction
301                NONE, // bluetoothInteraction
302                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
303                CallAudioState.ROUTE_EARPIECE, // expectedRoute
304                CallAudioState.ROUTE_EARPIECE // expectedAvailableRoutes
305        ));
306
307        params.add(new TestParameters(
308                "Disconnect headset during headset with bluetooth available", // name
309                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
310                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
311                NONE, // speakerInteraction
312                NONE, // bluetoothInteraction
313                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
314                CallAudioState.ROUTE_EARPIECE, // expectedRoute
315                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH // expectedAvailableR
316        ));
317
318        params.add(new TestParameters(
319                "Disconnect headset during bluetooth", // name
320                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
321                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
322                NONE, // speakerInteraction
323                NONE, // bluetoothInteraction
324                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
325                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
326                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH // expectedAvailableR
327        ));
328
329        params.add(new TestParameters(
330                "Disconnect headset during speakerphone", // name
331                CallAudioState.ROUTE_SPEAKER, // initialRoute
332                CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
333                NONE, // speakerInteraction
334                NONE, // bluetoothInteraction
335                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
336                CallAudioState.ROUTE_SPEAKER, // expectedRoute
337                CallAudioState.ROUTE_EARPIECE // expectedAvailableRoutes
338        ));
339
340        params.add(new TestParameters(
341                "Disconnect headset during speakerphone with bluetooth available", // name
342                CallAudioState.ROUTE_SPEAKER, // initialRoute
343                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
344                NONE, // speakerInteraction
345                NONE, // bluetoothInteraction
346                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
347                CallAudioState.ROUTE_SPEAKER, // expectedRoute
348                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH // expectedAvailableR
349        ));
350
351        params.add(new TestParameters(
352                "Connect bluetooth during earpiece", // name
353                CallAudioState.ROUTE_EARPIECE, // initialRoute
354                CallAudioState.ROUTE_EARPIECE, // availableRoutes
355                NONE, // speakerInteraction
356                ON, // bluetoothInteraction
357                CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
358                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
359                CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_EARPIECE // expectedAvailableR
360        ));
361
362        params.add(new TestParameters(
363                "Connect bluetooth during wired headset", // name
364                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
365                CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
366                NONE, // speakerInteraction
367                ON, // bluetoothInteraction
368                CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
369                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
370                CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_WIRED_HEADSET // expectedAvail
371        ));
372
373        params.add(new TestParameters(
374                "Connect bluetooth during speakerphone", // name
375                CallAudioState.ROUTE_SPEAKER, // initialRoute
376                CallAudioState.ROUTE_EARPIECE, // availableRoutes
377                OFF, // speakerInteraction
378                ON, // bluetoothInteraction
379                CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
380                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
381                CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_EARPIECE // expectedAvailableR
382        ));
383
384        params.add(new TestParameters(
385                "Disconnect bluetooth during bluetooth without headset in", // name
386                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
387                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
388                NONE, // speakerInteraction
389                OFF, // bluetoothInteraction
390                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
391                CallAudioState.ROUTE_EARPIECE, // expectedRoute
392                CallAudioState.ROUTE_EARPIECE // expectedAvailableRoutes
393        ));
394
395        params.add(new TestParameters(
396                "Disconnect bluetooth during bluetooth with headset in", // name
397                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
398                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
399                NONE, // speakerInteraction
400                OFF, // bluetoothInteraction
401                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
402                CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
403                CallAudioState.ROUTE_WIRED_HEADSET // expectedAvailableRoutes
404        ));
405
406        params.add(new TestParameters(
407                "Disconnect bluetooth during speakerphone", // name
408                CallAudioState.ROUTE_SPEAKER, // initialRoute
409                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
410                NONE, // speakerInteraction
411                NONE, // bluetoothInteraction
412                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
413                CallAudioState.ROUTE_SPEAKER, // expectedRoute
414                CallAudioState.ROUTE_WIRED_HEADSET // expectedAvailableRoutes
415        ));
416
417        params.add(new TestParameters(
418                "Disconnect bluetooth during earpiece", // name
419                CallAudioState.ROUTE_EARPIECE, // initialRoute
420                CallAudioState.ROUTE_EARPIECE| CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
421                NONE, // speakerInteraction
422                NONE, // bluetoothInteraction
423                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
424                CallAudioState.ROUTE_EARPIECE, // expectedRoute
425                CallAudioState.ROUTE_EARPIECE // expectedAvailableRoutes
426        ));
427
428        params.add(new TestParameters(
429                "Switch to speakerphone from earpiece", // name
430                CallAudioState.ROUTE_EARPIECE, // initialRoute
431                CallAudioState.ROUTE_EARPIECE, // availableRoutes
432                ON, // speakerInteraction
433                NONE, // bluetoothInteraction
434                CallAudioRouteStateMachine.SWITCH_SPEAKER, // action
435                CallAudioState.ROUTE_SPEAKER, // expectedRoute
436                CallAudioState.ROUTE_EARPIECE // expectedAvailableRoutes
437        ));
438
439        params.add(new TestParameters(
440                "Switch to speakerphone from headset", // name
441                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
442                CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
443                ON, // speakerInteraction
444                NONE, // bluetoothInteraction
445                CallAudioRouteStateMachine.SWITCH_SPEAKER, // action
446                CallAudioState.ROUTE_SPEAKER, // expectedRoute
447                CallAudioState.ROUTE_WIRED_HEADSET // expectedAvailableRoutes
448        ));
449
450        params.add(new TestParameters(
451                "Switch to speakerphone from bluetooth", // name
452                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
453                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
454                ON, // speakerInteraction
455                OFF, // bluetoothInteraction
456                CallAudioRouteStateMachine.SWITCH_SPEAKER, // action
457                CallAudioState.ROUTE_SPEAKER, // expectedRoute
458                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH // expectedAvail
459        ));
460
461        params.add(new TestParameters(
462                "Switch to earpiece from bluetooth", // name
463                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
464                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
465                NONE, // speakerInteraction
466                OFF, // bluetoothInteraction
467                CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
468                CallAudioState.ROUTE_EARPIECE, // expectedRoute
469                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH // expectedAvailableR
470        ));
471
472        params.add(new TestParameters(
473                "Switch to earpiece from speakerphone", // name
474                CallAudioState.ROUTE_SPEAKER, // initialRoute
475                CallAudioState.ROUTE_EARPIECE, // availableRoutes
476                OFF, // speakerInteraction
477                NONE, // bluetoothInteraction
478                CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
479                CallAudioState.ROUTE_EARPIECE, // expectedRoute
480                CallAudioState.ROUTE_EARPIECE // expectedAvailableRoutes
481        ));
482
483        params.add(new TestParameters(
484                "Switch to bluetooth from speakerphone", // name
485                CallAudioState.ROUTE_SPEAKER, // initialRoute
486                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
487                OFF, // speakerInteraction
488                ON, // bluetoothInteraction
489                CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action
490                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
491                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH // expectedAvailableR
492        ));
493
494        params.add(new TestParameters(
495                "Switch to bluetooth from earpiece", // name
496                CallAudioState.ROUTE_EARPIECE, // initialRoute
497                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
498                NONE, // speakerInteraction
499                ON, // bluetoothInteraction
500                CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action
501                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
502                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH // expectedAvailableR
503        ));
504
505        params.add(new TestParameters(
506                "Switch to bluetooth from wired headset", // name
507                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
508                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
509                NONE, // speakerInteraction
510                ON, // bluetoothInteraction
511                CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action
512                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
513                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH // expectedAvail
514        ));
515
516        return params;
517    }
518
519    private void runParametrizedTestCaseWithFocus(TestParameters params) {
520        resetMocks();
521
522        // Construct a fresh state machine on every case
523        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
524                mContext,
525                mockCallsManager,
526                mockBluetoothManager,
527                mockWiredHeadsetManager,
528                mockStatusBarNotifier,
529                mAudioServiceFactory);
530
531        // Set up bluetooth and speakerphone state
532        when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn(
533                params.initialRoute == CallAudioState.ROUTE_BLUETOOTH);
534        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(
535                (params.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0
536                        || (params.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0);
537        when(mockAudioManager.isSpeakerphoneOn()).thenReturn(
538                params.initialRoute == CallAudioState.ROUTE_SPEAKER);
539
540        // Set the initial CallAudioState object
541        CallAudioState initState = new CallAudioState(false,
542                params.initialRoute, (params.availableRoutes | CallAudioState.ROUTE_SPEAKER));
543        stateMachine.initialize(initState);
544        // Make the state machine have focus so that we actually do something
545        stateMachine.sendMessage(CallAudioRouteStateMachine.SWITCH_FOCUS,
546                CallAudioRouteStateMachine.HAS_FOCUS);
547        stateMachine.sendMessage(params.action);
548
549        // Verify interactions with the speakerphone and bluetooth systems
550        switch(params.bluetoothInteraction) {
551            case NONE:
552                verify(mockBluetoothManager, never()).disconnectBluetoothAudio();
553                verify(mockBluetoothManager, never()).connectBluetoothAudio();
554                break;
555            case ON:
556                verify(mockBluetoothManager, timeout(TEST_TIMEOUT)).connectBluetoothAudio();
557                verify(mockBluetoothManager, never()).disconnectBluetoothAudio();
558                break;
559            case OFF:
560                verify(mockBluetoothManager, never()).connectBluetoothAudio();
561                verify(mockBluetoothManager, timeout(TEST_TIMEOUT)).disconnectBluetoothAudio();
562        }
563
564        switch (params.speakerInteraction) {
565            case NONE:
566                verify(mockAudioManager, never()).setSpeakerphoneOn(any(Boolean.class));
567                break;
568            case ON: // fall through
569            case OFF:
570                verify(mockAudioManager, timeout(TEST_TIMEOUT)).setSpeakerphoneOn(
571                        params.speakerInteraction == ON);
572        }
573
574        // Verify the end state
575        CallAudioState expectedState = new CallAudioState(false, params.expectedRoute,
576                params.expectedAvailableRoutes | CallAudioState.ROUTE_SPEAKER);
577        verifyNewSystemCallAudioState(initState, expectedState);
578    }
579
580    private void runParametrizedTestCaseWithoutFocus(TestParameters params) {
581        resetMocks();
582
583        // Construct a fresh state machine on every case
584        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
585                mContext,
586                mockCallsManager,
587                mockBluetoothManager,
588                mockWiredHeadsetManager,
589                mockStatusBarNotifier,
590                mAudioServiceFactory);
591
592        // Set up bluetooth and speakerphone state
593        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(
594                (params.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0
595                || (params.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0);
596        when(mockAudioManager.isSpeakerphoneOn()).thenReturn(
597                params.initialRoute == CallAudioState.ROUTE_SPEAKER);
598
599        // Set the initial CallAudioState object
600        CallAudioState initState = new CallAudioState(false,
601                params.initialRoute, (params.availableRoutes | CallAudioState.ROUTE_SPEAKER));
602        stateMachine.initialize(initState);
603        // Omit the focus-getting statement
604        stateMachine.sendMessage(params.action);
605        try {
606            Thread.sleep(100L);
607        } catch (InterruptedException e) {
608            // Just a pause to make sure the state machine handler thread has a chance to update
609            // its state. Do nothing.
610        }
611
612        // Verify that no substantive interactions have taken place with the rest of the system
613        verify(mockBluetoothManager, never()).disconnectBluetoothAudio();
614        verify(mockBluetoothManager, never()).connectBluetoothAudio();
615        verify(mockAudioManager, never()).setSpeakerphoneOn(any(Boolean.class));
616        verify(mockCallsManager, never()).onCallAudioStateChanged(any(CallAudioState.class),
617                any(CallAudioState.class));
618        verify(mockConnectionServiceWrapper, never()).onCallAudioStateChanged(
619                any(Call.class), any(CallAudioState.class));
620
621        // Verify the end state
622        CallAudioState expectedState = new CallAudioState(false, params.expectedRoute,
623                params.expectedAvailableRoutes | CallAudioState.ROUTE_SPEAKER);
624        assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
625    }
626
627    private void verifyNewSystemCallAudioState(CallAudioState expectedOldState,
628            CallAudioState expectedNewState) {
629        ArgumentCaptor<CallAudioState> oldStateCaptor = ArgumentCaptor.forClass(
630                CallAudioState.class);
631        ArgumentCaptor<CallAudioState> newStateCaptor1 = ArgumentCaptor.forClass(
632                CallAudioState.class);
633        ArgumentCaptor<CallAudioState> newStateCaptor2 = ArgumentCaptor.forClass(
634                CallAudioState.class);
635        verify(mockCallsManager, timeout(TEST_TIMEOUT).atLeastOnce()).onCallAudioStateChanged(
636                oldStateCaptor.capture(), newStateCaptor1.capture());
637        verify(mockConnectionServiceWrapper, timeout(TEST_TIMEOUT).atLeastOnce())
638                .onCallAudioStateChanged(same(fakeCall), newStateCaptor2.capture());
639
640        assertTrue(oldStateCaptor.getValue().equals(expectedOldState));
641        assertTrue(newStateCaptor1.getValue().equals(expectedNewState));
642        assertTrue(newStateCaptor2.getValue().equals(expectedNewState));
643    }
644
645    private void resetMocks() {
646        reset(mockAudioManager, mockBluetoothManager, mockCallsManager,
647                mockConnectionServiceWrapper);
648        when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall);
649        doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class),
650                any(CallAudioState.class));
651    }
652}