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