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