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 
17 package com.android.server.telecom.tests;
18 
19 import android.media.ToneGenerator;
20 import android.telecom.DisconnectCause;
21 import android.test.suitebuilder.annotation.MediumTest;
22 import android.util.SparseArray;
23 
24 import com.android.server.telecom.Call;
25 import com.android.server.telecom.CallAudioModeStateMachine;
26 import com.android.server.telecom.CallAudioRouteStateMachine;
27 import com.android.server.telecom.CallState;
28 import com.android.server.telecom.CallsManager;
29 import com.android.server.telecom.CallAudioManager;
30 import com.android.server.telecom.DtmfLocalTonePlayer;
31 import com.android.server.telecom.InCallTonePlayer;
32 import com.android.server.telecom.RingbackPlayer;
33 import com.android.server.telecom.Ringer;
34 
35 import org.mockito.ArgumentCaptor;
36 import org.mockito.Mock;
37 
38 import java.util.LinkedHashSet;
39 
40 import static org.mockito.Matchers.any;
41 import static org.mockito.Matchers.anyInt;
42 import static org.mockito.Matchers.eq;
43 import static org.mockito.Mockito.doAnswer;
44 import static org.mockito.Mockito.mock;
45 import static org.mockito.Mockito.times;
46 import static org.mockito.Mockito.verify;
47 import static org.mockito.Mockito.when;
48 
49 public class CallAudioManagerTest extends TelecomTestCase {
50     @Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
51     @Mock private CallsManager mCallsManager;
52     @Mock private CallAudioModeStateMachine mCallAudioModeStateMachine;
53     @Mock private InCallTonePlayer.Factory mPlayerFactory;
54     @Mock private Ringer mRinger;
55     @Mock private RingbackPlayer mRingbackPlayer;
56     @Mock private DtmfLocalTonePlayer mDtmfLocalTonePlayer;
57 
58     private CallAudioManager mCallAudioManager;
59 
60     @Override
setUp()61     public void setUp() throws Exception {
62         super.setUp();
63         doAnswer((invocation) -> {
64             InCallTonePlayer mockInCallTonePlayer = mock(InCallTonePlayer.class);
65             doAnswer((invocation2) -> {
66                 mCallAudioManager.setIsTonePlaying(true);
67                 return null;
68             }).when(mockInCallTonePlayer).startTone();
69             return mockInCallTonePlayer;
70         }).when(mPlayerFactory).createPlayer(anyInt());
71         mCallAudioManager = new CallAudioManager(
72                 mCallAudioRouteStateMachine,
73                 mCallsManager,
74                 mCallAudioModeStateMachine,
75                 mPlayerFactory,
76                 mRinger,
77                 mRingbackPlayer,
78                 mDtmfLocalTonePlayer);
79     }
80 
81     @MediumTest
testSingleIncomingCallFlowWithoutMTSpeedUp()82     public void testSingleIncomingCallFlowWithoutMTSpeedUp() {
83         Call call = createIncomingCall();
84         when(call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO))
85                 .thenReturn(false);
86 
87         ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
88                 ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
89         // Answer the incoming call
90         mCallAudioManager.onIncomingCallAnswered(call);
91         when(call.getState()).thenReturn(CallState.ACTIVE);
92         mCallAudioManager.onCallStateChanged(call, CallState.RINGING, CallState.ACTIVE);
93         verify(mCallAudioModeStateMachine).sendMessageWithArgs(
94                 eq(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS), captor.capture());
95         CallAudioModeStateMachine.MessageArgs correctArgs =
96                 new CallAudioModeStateMachine.MessageArgs(
97                         true, // hasActiveOrDialingCalls
98                         false, // hasRingingCalls
99                         false, // hasHoldingCalls
100                         false, // isTonePlaying
101                         false, // foregroundCallIsVoip
102                         null // session
103                 );
104         assertMessageArgEquality(correctArgs, captor.getValue());
105         verify(mCallAudioModeStateMachine).sendMessageWithArgs(
106                 eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
107         assertMessageArgEquality(correctArgs, captor.getValue());
108 
109         disconnectCall(call);
110         stopTone();
111 
112         mCallAudioManager.onCallRemoved(call);
113         verifyProperCleanup();
114     }
115 
116     @MediumTest
testSingleIncomingCallFlowWithMTSpeedUp()117     public void testSingleIncomingCallFlowWithMTSpeedUp() {
118         Call call = createIncomingCall();
119         when(call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO))
120                 .thenReturn(true);
121 
122         ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
123                 ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
124         // Answer the incoming call
125         mCallAudioManager.onIncomingCallAnswered(call);
126         verify(mCallAudioModeStateMachine).sendMessageWithArgs(
127                 eq(CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL), captor.capture());
128         CallAudioModeStateMachine.MessageArgs correctArgs =
129                 new CallAudioModeStateMachine.MessageArgs(
130                         true, // hasActiveOrDialingCalls
131                         false, // hasRingingCalls
132                         false, // hasHoldingCalls
133                         false, // isTonePlaying
134                         false, // foregroundCallIsVoip
135                         null // session
136                 );
137         assertMessageArgEquality(correctArgs, captor.getValue());
138         assertMessageArgEquality(correctArgs, captor.getValue());
139         when(call.getState()).thenReturn(CallState.ACTIVE);
140         mCallAudioManager.onCallStateChanged(call, CallState.RINGING, CallState.ACTIVE);
141 
142         disconnectCall(call);
143         stopTone();
144 
145         mCallAudioManager.onCallRemoved(call);
146         verifyProperCleanup();
147     }
148 
149     @MediumTest
testSingleOutgoingCall()150     public void testSingleOutgoingCall() {
151         Call call = mock(Call.class);
152         when(call.getState()).thenReturn(CallState.CONNECTING);
153 
154         mCallAudioManager.onCallAdded(call);
155         assertEquals(call, mCallAudioManager.getForegroundCall());
156         ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
157                 ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
158         verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
159                 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
160         verify(mCallAudioModeStateMachine).sendMessageWithArgs(
161                 eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
162         CallAudioModeStateMachine.MessageArgs expectedArgs =
163                 new CallAudioModeStateMachine.MessageArgs(
164                         true, // hasActiveOrDialingCalls
165                         false, // hasRingingCalls
166                         false, // hasHoldingCalls
167                         false, // isTonePlaying
168                         false, // foregroundCallIsVoip
169                         null // session
170                 );
171         assertMessageArgEquality(expectedArgs, captor.getValue());
172 
173         when(call.getState()).thenReturn(CallState.DIALING);
174         mCallAudioManager.onCallStateChanged(call, CallState.CONNECTING, CallState.DIALING);
175         verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
176                 eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
177         assertMessageArgEquality(expectedArgs, captor.getValue());
178         verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
179                 anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
180 
181 
182         when(call.getState()).thenReturn(CallState.ACTIVE);
183         mCallAudioManager.onCallStateChanged(call, CallState.DIALING, CallState.ACTIVE);
184         verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
185                 eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
186         assertMessageArgEquality(expectedArgs, captor.getValue());
187         verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
188                 anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
189 
190         disconnectCall(call);
191         stopTone();
192 
193         mCallAudioManager.onCallRemoved(call);
194         verifyProperCleanup();
195     }
196 
createIncomingCall()197     private Call createIncomingCall() {
198         Call call = mock(Call.class);
199         when(call.getState()).thenReturn(CallState.RINGING);
200 
201         mCallAudioManager.onCallAdded(call);
202         assertEquals(call, mCallAudioManager.getForegroundCall());
203         ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
204                 ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
205         verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
206                 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
207         verify(mCallAudioModeStateMachine).sendMessageWithArgs(
208                 eq(CallAudioModeStateMachine.NEW_RINGING_CALL), captor.capture());
209         assertMessageArgEquality(new CallAudioModeStateMachine.MessageArgs(
210                 false, // hasActiveOrDialingCalls
211                 true, // hasRingingCalls
212                 false, // hasHoldingCalls
213                 false, // isTonePlaying
214                 false, // foregroundCallIsVoip
215                 null // session
216         ), captor.getValue());
217 
218         return call;
219     }
220 
disconnectCall(Call call)221     private void disconnectCall(Call call) {
222         ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
223                 ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
224         CallAudioModeStateMachine.MessageArgs correctArgs;
225 
226         when(call.getState()).thenReturn(CallState.DISCONNECTED);
227         when(call.getDisconnectCause()).thenReturn(new DisconnectCause(DisconnectCause.LOCAL,
228                 "", "", "", ToneGenerator.TONE_PROP_PROMPT));
229 
230         mCallAudioManager.onCallStateChanged(call, CallState.ACTIVE, CallState.DISCONNECTED);
231         verify(mPlayerFactory).createPlayer(InCallTonePlayer.TONE_CALL_ENDED);
232         correctArgs = new CallAudioModeStateMachine.MessageArgs(
233                 false, // hasActiveOrDialingCalls
234                 false, // hasRingingCalls
235                 false, // hasHoldingCalls
236                 true, // isTonePlaying
237                 false, // foregroundCallIsVoip
238                 null // session
239         );
240         verify(mCallAudioModeStateMachine).sendMessageWithArgs(
241                 eq(CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS), captor.capture());
242         assertMessageArgEquality(correctArgs, captor.getValue());
243         verify(mCallAudioModeStateMachine).sendMessageWithArgs(
244                 eq(CallAudioModeStateMachine.TONE_STARTED_PLAYING), captor.capture());
245         assertMessageArgEquality(correctArgs, captor.getValue());
246     }
247 
stopTone()248     private void stopTone() {
249         ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
250                 ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
251         mCallAudioManager.setIsTonePlaying(false);
252         CallAudioModeStateMachine.MessageArgs correctArgs =
253                 new CallAudioModeStateMachine.MessageArgs(
254                         false, // hasActiveOrDialingCalls
255                         false, // hasRingingCalls
256                         false, // hasHoldingCalls
257                         false, // isTonePlaying
258                         false, // foregroundCallIsVoip
259                         null // session
260                 );
261         verify(mCallAudioModeStateMachine).sendMessageWithArgs(
262                 eq(CallAudioModeStateMachine.TONE_STOPPED_PLAYING), captor.capture());
263         assertMessageArgEquality(correctArgs, captor.getValue());
264     }
265 
verifyProperCleanup()266     private void verifyProperCleanup() {
267         assertEquals(0, mCallAudioManager.getTrackedCalls().size());
268         SparseArray<LinkedHashSet<Call>> callStateToCalls = mCallAudioManager.getCallStateToCalls();
269         for (int i = 0; i < callStateToCalls.size(); i++) {
270             assertEquals(0, callStateToCalls.valueAt(i).size());
271         }
272     }
273 
assertMessageArgEquality(CallAudioModeStateMachine.MessageArgs expected, CallAudioModeStateMachine.MessageArgs actual)274     private void assertMessageArgEquality(CallAudioModeStateMachine.MessageArgs expected,
275             CallAudioModeStateMachine.MessageArgs actual) {
276         assertEquals(expected.hasActiveOrDialingCalls, actual.hasActiveOrDialingCalls);
277         assertEquals(expected.hasHoldingCalls, actual.hasHoldingCalls);
278         assertEquals(expected.hasRingingCalls, actual.hasRingingCalls);
279         assertEquals(expected.isTonePlaying, actual.isTonePlaying);
280         assertEquals(expected.foregroundCallIsVoip, actual.foregroundCallIsVoip);
281     }
282 }
283