1/*
2 * Copyright (C) 2016 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.media.ToneGenerator;
20import android.telecom.DisconnectCause;
21import android.test.suitebuilder.annotation.MediumTest;
22import android.util.SparseArray;
23
24import com.android.server.telecom.Call;
25import com.android.server.telecom.CallAudioModeStateMachine;
26import com.android.server.telecom.CallAudioRouteStateMachine;
27import com.android.server.telecom.CallState;
28import com.android.server.telecom.CallsManager;
29import com.android.server.telecom.CallAudioManager;
30import com.android.server.telecom.DtmfLocalTonePlayer;
31import com.android.server.telecom.InCallTonePlayer;
32import com.android.server.telecom.RingbackPlayer;
33import com.android.server.telecom.Ringer;
34import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
35
36import org.junit.Before;
37import org.junit.Test;
38import org.junit.runner.RunWith;
39import org.junit.runners.JUnit4;
40import org.mockito.ArgumentCaptor;
41import org.mockito.Mock;
42
43import java.util.LinkedHashSet;
44import java.util.List;
45import java.util.stream.Collectors;
46
47import static org.junit.Assert.assertEquals;
48import static org.junit.Assert.assertTrue;
49import static org.mockito.Matchers.any;
50import static org.mockito.Matchers.anyInt;
51import static org.mockito.Matchers.eq;
52import static org.mockito.Mockito.atLeastOnce;
53import static org.mockito.Mockito.doAnswer;
54import static org.mockito.Mockito.mock;
55import static org.mockito.Mockito.times;
56import static org.mockito.Mockito.verify;
57import static org.mockito.Mockito.when;
58
59@RunWith(JUnit4.class)
60public class CallAudioManagerTest extends TelecomTestCase {
61    @Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
62    @Mock private CallsManager mCallsManager;
63    @Mock private CallAudioModeStateMachine mCallAudioModeStateMachine;
64    @Mock private InCallTonePlayer.Factory mPlayerFactory;
65    @Mock private Ringer mRinger;
66    @Mock private RingbackPlayer mRingbackPlayer;
67    @Mock private DtmfLocalTonePlayer mDtmfLocalTonePlayer;
68    @Mock private BluetoothStateReceiver mBluetoothStateReceiver;
69
70    private CallAudioManager mCallAudioManager;
71
72    @Override
73    @Before
74    public void setUp() throws Exception {
75        super.setUp();
76        doAnswer((invocation) -> {
77            InCallTonePlayer mockInCallTonePlayer = mock(InCallTonePlayer.class);
78            doAnswer((invocation2) -> {
79                mCallAudioManager.setIsTonePlaying(true);
80                return null;
81            }).when(mockInCallTonePlayer).startTone();
82            return mockInCallTonePlayer;
83        }).when(mPlayerFactory).createPlayer(anyInt());
84        mCallAudioManager = new CallAudioManager(
85                mCallAudioRouteStateMachine,
86                mCallsManager,
87                mCallAudioModeStateMachine,
88                mPlayerFactory,
89                mRinger,
90                mRingbackPlayer,
91                mBluetoothStateReceiver,
92                mDtmfLocalTonePlayer);
93    }
94
95    @MediumTest
96    @Test
97    public void testUnmuteOfSecondIncomingCall() {
98        // Start with a single incoming call.
99        Call call = createIncomingCall();
100        when(call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO))
101                .thenReturn(false);
102        when(call.getId()).thenReturn("1");
103
104        ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
105                ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
106        // Answer the incoming call
107        mCallAudioManager.onIncomingCallAnswered(call);
108        when(call.getState()).thenReturn(CallState.ACTIVE);
109        mCallAudioManager.onCallStateChanged(call, CallState.RINGING, CallState.ACTIVE);
110        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
111                eq(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS), captor.capture());
112        CallAudioModeStateMachine.MessageArgs correctArgs =
113                new CallAudioModeStateMachine.MessageArgs(
114                        true, // hasActiveOrDialingCalls
115                        false, // hasRingingCalls
116                        false, // hasHoldingCalls
117                        false, // isTonePlaying
118                        false, // foregroundCallIsVoip
119                        null // session
120                );
121        assertMessageArgEquality(correctArgs, captor.getValue());
122        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
123                eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
124        assertMessageArgEquality(correctArgs, captor.getValue());
125
126        // Mute the current ongoing call.
127        mCallAudioManager.mute(true);
128
129        // Create a second incoming call.
130        Call call2 = mock(Call.class);
131        when(call2.getState()).thenReturn(CallState.RINGING);
132        when(call2.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO))
133                .thenReturn(false);
134        when(call2.getId()).thenReturn("2");
135        mCallAudioManager.onCallAdded(call2);
136
137        // Answer the incoming call
138        mCallAudioManager.onIncomingCallAnswered(call);
139
140        // Capture the calls to sendMessageWithSessionInfo; we want to look for mute on and off
141        // messages and make sure that there was a mute on before the mute off.
142        ArgumentCaptor<Integer> muteCaptor = ArgumentCaptor.forClass(Integer.class);
143        verify(mCallAudioRouteStateMachine, atLeastOnce())
144                .sendMessageWithSessionInfo(muteCaptor.capture());
145        List<Integer> values = muteCaptor.getAllValues();
146        values = values.stream()
147                .filter(value -> value == CallAudioRouteStateMachine.MUTE_ON ||
148                        value == CallAudioRouteStateMachine.MUTE_OFF)
149                .collect(Collectors.toList());
150
151        // Make sure we got a mute on and a mute off.
152        assertTrue(values.contains(CallAudioRouteStateMachine.MUTE_ON));
153        assertTrue(values.contains(CallAudioRouteStateMachine.MUTE_OFF));
154        // And that the mute on happened before the off.
155        assertTrue(values.indexOf(CallAudioRouteStateMachine.MUTE_ON) < values
156                .lastIndexOf(CallAudioRouteStateMachine.MUTE_OFF));
157    }
158
159    @MediumTest
160    @Test
161    public void testSingleIncomingCallFlowWithoutMTSpeedUp() {
162        Call call = createIncomingCall();
163        when(call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO))
164                .thenReturn(false);
165
166        ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
167                ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
168        // Answer the incoming call
169        mCallAudioManager.onIncomingCallAnswered(call);
170        when(call.getState()).thenReturn(CallState.ACTIVE);
171        mCallAudioManager.onCallStateChanged(call, CallState.RINGING, CallState.ACTIVE);
172        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
173                eq(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS), captor.capture());
174        CallAudioModeStateMachine.MessageArgs correctArgs =
175                new CallAudioModeStateMachine.MessageArgs(
176                        true, // hasActiveOrDialingCalls
177                        false, // hasRingingCalls
178                        false, // hasHoldingCalls
179                        false, // isTonePlaying
180                        false, // foregroundCallIsVoip
181                        null // session
182                );
183        assertMessageArgEquality(correctArgs, captor.getValue());
184        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
185                eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
186        assertMessageArgEquality(correctArgs, captor.getValue());
187
188        disconnectCall(call);
189        stopTone();
190
191        mCallAudioManager.onCallRemoved(call);
192        verifyProperCleanup();
193    }
194
195    @MediumTest
196    @Test
197    public void testSingleIncomingCallFlowWithMTSpeedUp() {
198        Call call = createIncomingCall();
199        when(call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO))
200                .thenReturn(true);
201
202        ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
203                ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
204        // Answer the incoming call
205        mCallAudioManager.onIncomingCallAnswered(call);
206        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
207                eq(CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL), captor.capture());
208        CallAudioModeStateMachine.MessageArgs correctArgs =
209                new CallAudioModeStateMachine.MessageArgs(
210                        true, // hasActiveOrDialingCalls
211                        false, // hasRingingCalls
212                        false, // hasHoldingCalls
213                        false, // isTonePlaying
214                        false, // foregroundCallIsVoip
215                        null // session
216                );
217        assertMessageArgEquality(correctArgs, captor.getValue());
218        assertMessageArgEquality(correctArgs, captor.getValue());
219        when(call.getState()).thenReturn(CallState.ACTIVE);
220        mCallAudioManager.onCallStateChanged(call, CallState.RINGING, CallState.ACTIVE);
221
222        disconnectCall(call);
223        stopTone();
224
225        mCallAudioManager.onCallRemoved(call);
226        verifyProperCleanup();
227    }
228
229    @MediumTest
230    @Test
231    public void testSingleOutgoingCall() {
232        Call call = mock(Call.class);
233        when(call.getState()).thenReturn(CallState.CONNECTING);
234
235        mCallAudioManager.onCallAdded(call);
236        assertEquals(call, mCallAudioManager.getForegroundCall());
237        ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
238                ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
239        verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
240                CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
241        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
242                eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
243        CallAudioModeStateMachine.MessageArgs expectedArgs =
244                new CallAudioModeStateMachine.MessageArgs(
245                        true, // hasActiveOrDialingCalls
246                        false, // hasRingingCalls
247                        false, // hasHoldingCalls
248                        false, // isTonePlaying
249                        false, // foregroundCallIsVoip
250                        null // session
251                );
252        assertMessageArgEquality(expectedArgs, captor.getValue());
253
254        when(call.getState()).thenReturn(CallState.DIALING);
255        mCallAudioManager.onCallStateChanged(call, CallState.CONNECTING, CallState.DIALING);
256        verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
257                eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
258        assertMessageArgEquality(expectedArgs, captor.getValue());
259        verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
260                anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
261
262
263        when(call.getState()).thenReturn(CallState.ACTIVE);
264        mCallAudioManager.onCallStateChanged(call, CallState.DIALING, CallState.ACTIVE);
265        verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
266                eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
267        assertMessageArgEquality(expectedArgs, captor.getValue());
268        verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
269                anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
270
271        disconnectCall(call);
272        stopTone();
273
274        mCallAudioManager.onCallRemoved(call);
275        verifyProperCleanup();
276    }
277
278    private Call createIncomingCall() {
279        Call call = mock(Call.class);
280        when(call.getState()).thenReturn(CallState.RINGING);
281
282        mCallAudioManager.onCallAdded(call);
283        assertEquals(call, mCallAudioManager.getForegroundCall());
284        ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
285                ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
286        verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
287                CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
288        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
289                eq(CallAudioModeStateMachine.NEW_RINGING_CALL), captor.capture());
290        assertMessageArgEquality(new CallAudioModeStateMachine.MessageArgs(
291                false, // hasActiveOrDialingCalls
292                true, // hasRingingCalls
293                false, // hasHoldingCalls
294                false, // isTonePlaying
295                false, // foregroundCallIsVoip
296                null // session
297        ), captor.getValue());
298
299        return call;
300    }
301
302    private void disconnectCall(Call call) {
303        ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
304                ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
305        CallAudioModeStateMachine.MessageArgs correctArgs;
306
307        when(call.getState()).thenReturn(CallState.DISCONNECTED);
308        when(call.getDisconnectCause()).thenReturn(new DisconnectCause(DisconnectCause.LOCAL,
309                "", "", "", ToneGenerator.TONE_PROP_PROMPT));
310
311        mCallAudioManager.onCallStateChanged(call, CallState.ACTIVE, CallState.DISCONNECTED);
312        verify(mPlayerFactory).createPlayer(InCallTonePlayer.TONE_CALL_ENDED);
313        correctArgs = new CallAudioModeStateMachine.MessageArgs(
314                false, // hasActiveOrDialingCalls
315                false, // hasRingingCalls
316                false, // hasHoldingCalls
317                true, // isTonePlaying
318                false, // foregroundCallIsVoip
319                null // session
320        );
321        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
322                eq(CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS), captor.capture());
323        assertMessageArgEquality(correctArgs, captor.getValue());
324        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
325                eq(CallAudioModeStateMachine.TONE_STARTED_PLAYING), captor.capture());
326        assertMessageArgEquality(correctArgs, captor.getValue());
327    }
328
329    private void stopTone() {
330        ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
331                ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
332        mCallAudioManager.setIsTonePlaying(false);
333        CallAudioModeStateMachine.MessageArgs correctArgs =
334                new CallAudioModeStateMachine.MessageArgs(
335                        false, // hasActiveOrDialingCalls
336                        false, // hasRingingCalls
337                        false, // hasHoldingCalls
338                        false, // isTonePlaying
339                        false, // foregroundCallIsVoip
340                        null // session
341                );
342        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
343                eq(CallAudioModeStateMachine.TONE_STOPPED_PLAYING), captor.capture());
344        assertMessageArgEquality(correctArgs, captor.getValue());
345    }
346
347    private void verifyProperCleanup() {
348        assertEquals(0, mCallAudioManager.getTrackedCalls().size());
349        SparseArray<LinkedHashSet<Call>> callStateToCalls = mCallAudioManager.getCallStateToCalls();
350        for (int i = 0; i < callStateToCalls.size(); i++) {
351            assertEquals(0, callStateToCalls.valueAt(i).size());
352        }
353    }
354
355    private void assertMessageArgEquality(CallAudioModeStateMachine.MessageArgs expected,
356            CallAudioModeStateMachine.MessageArgs actual) {
357        assertEquals(expected.hasActiveOrDialingCalls, actual.hasActiveOrDialingCalls);
358        assertEquals(expected.hasHoldingCalls, actual.hasHoldingCalls);
359        assertEquals(expected.hasRingingCalls, actual.hasRingingCalls);
360        assertEquals(expected.isTonePlaying, actual.isTonePlaying);
361        assertEquals(expected.foregroundCallIsVoip, actual.foregroundCallIsVoip);
362    }
363}
364