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