blob: 759309c423842cd4821cd1177fb0971a1946e8bc [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.telephony.imsphone;
import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.PersistableBundle;
import android.support.test.filters.FlakyTest;
import android.telecom.VideoProfile;
import android.telephony.CarrierConfigManager;
import android.telephony.DisconnectCause;
import android.telephony.PhoneNumberUtils;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.telephony.ims.ImsCallProfile;
import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ImsStreamMediaProfile;
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.ims.ImsCall;
import com.android.ims.ImsConfig;
import com.android.ims.ImsException;
import com.android.ims.internal.IImsCallSession;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyTest;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
public class ImsPhoneCallTrackerTest extends TelephonyTest {
private ImsPhoneCallTracker mCTUT;
private ImsCTHandlerThread mImsCTHandlerThread;
private MmTelFeature.Listener mMmTelListener;
private ImsRegistrationImplBase.Callback mRegistrationCallback;
private ImsFeature.CapabilityCallback mCapabilityCallback;
private ImsCall.Listener mImsCallListener;
private ImsCall mImsCall;
private ImsCall mSecondImsCall;
private Bundle mBundle = new Bundle();
private int mServiceId;
@Mock
private ImsCallSession mImsCallSession;
@Mock
private SharedPreferences mSharedPreferences;
@Mock
private ImsPhoneConnection.Listener mImsPhoneConnectionListener;
@Mock
private ImsConfig mImsConfig;
private Handler mCTHander;
private class ImsCTHandlerThread extends HandlerThread {
private ImsCTHandlerThread(String name) {
super(name);
}
@Override
public void onLooperPrepared() {
mCTUT = new ImsPhoneCallTracker(mImsPhone);
mCTUT.addReasonCodeRemapping(null, "Wifi signal lost.", ImsReasonInfo.CODE_WIFI_LOST);
mCTUT.addReasonCodeRemapping(501, "Call answered elsewhere.",
ImsReasonInfo.CODE_ANSWERED_ELSEWHERE);
mCTUT.addReasonCodeRemapping(510, "Call answered elsewhere.",
ImsReasonInfo.CODE_ANSWERED_ELSEWHERE);
mCTUT.setDataEnabled(true);
mCTHander = new Handler(mCTUT.getLooper());
setReady(true);
}
}
private void imsCallMocking(final ImsCall imsCall) throws Exception {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
// trigger the listener on accept call
if (mImsCallListener != null) {
mImsCallListener.onCallStarted(imsCall);
}
return null;
}
}).when(imsCall).accept(anyInt());
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
// trigger the listener on reject call
int reasonCode = (int) invocation.getArguments()[0];
if (mImsCallListener != null) {
mImsCallListener.onCallStartFailed(imsCall, new ImsReasonInfo(reasonCode, -1));
mImsCallListener.onCallTerminated(imsCall, new ImsReasonInfo(reasonCode, -1));
}
return null;
}
}).when(imsCall).reject(anyInt());
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
// trigger the listener on reject call
int reasonCode = (int) invocation.getArguments()[0];
if (mImsCallListener != null) {
mImsCallListener.onCallTerminated(imsCall, new ImsReasonInfo(reasonCode, -1));
}
return null;
}
}).when(imsCall).terminate(anyInt());
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
if (mImsCallListener != null) {
mImsCallListener.onCallHeld(imsCall);
}
return null;
}
}).when(imsCall).hold();
imsCall.attachSession(mImsCallSession);
}
@Before
public void setUp() throws Exception {
super.setUp(this.getClass().getSimpleName());
mServiceId = 0;
mImsCallProfile.mCallExtras = mBundle;
mImsManagerInstances.put(mImsPhone.getPhoneId(), mImsManager);
mImsCall = spy(new ImsCall(mContext, mImsCallProfile));
mSecondImsCall = spy(new ImsCall(mContext, mImsCallProfile));
mImsPhoneConnectionListener = mock(ImsPhoneConnection.Listener.class);
imsCallMocking(mImsCall);
imsCallMocking(mSecondImsCall);
doReturn(ImsFeature.STATE_READY).when(mImsManager).getImsServiceState();
doReturn(mImsCallProfile).when(mImsManager).createCallProfile(anyInt(), anyInt());
doAnswer(invocation -> {
mMmTelListener = (MmTelFeature.Listener) invocation.getArguments()[0];
return null;
}).when(mImsManager).open(any(MmTelFeature.Listener.class));
doAnswer(new Answer<ImsCall>() {
@Override
public ImsCall answer(InvocationOnMock invocation) throws Throwable {
mImsCallListener =
(ImsCall.Listener) invocation.getArguments()[2];
mImsCall.setListener(mImsCallListener);
return mImsCall;
}
}).when(mImsManager).takeCall(any(), any(), any());
doAnswer(new Answer<ImsCall>() {
@Override
public ImsCall answer(InvocationOnMock invocation) throws Throwable {
mImsCallListener =
(ImsCall.Listener) invocation.getArguments()[2];
mSecondImsCall.setListener(mImsCallListener);
return mSecondImsCall;
}
}).when(mImsManager).makeCall(eq(mImsCallProfile), (String []) any(),
(ImsCall.Listener) any());
doAnswer(invocation -> {
mRegistrationCallback = invocation.getArgument(0);
return mRegistrationCallback;
}).when(mImsManager).addRegistrationCallback(any(ImsRegistrationImplBase.Callback.class));
doAnswer(invocation -> {
mCapabilityCallback = (ImsFeature.CapabilityCallback) invocation.getArguments()[0];
return mCapabilityCallback;
}).when(mImsManager).addCapabilitiesCallback(any(ImsFeature.CapabilityCallback.class));
doReturn(mImsConfig).when(mImsManager).getConfigInterface();
doNothing().when(mImsManager).addNotifyStatusChangedCallbackIfAvailable(any());
mImsCTHandlerThread = new ImsCTHandlerThread(this.getClass().getSimpleName());
mImsCTHandlerThread.start();
waitUntilReady();
logd("ImsPhoneCallTracker initiated");
/* Make sure getImsService is triggered on handler */
waitForHandlerAction(mCTHander, 100);
}
@After
public void tearDown() throws Exception {
mCTUT = null;
mImsCTHandlerThread.quit();
super.tearDown();
}
@Test
@SmallTest
public void testImsRegistered() {
// when IMS is registered
mRegistrationCallback.onRegistered(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
// then service state should be IN_SERVICE and ImsPhone state set to registered
verify(mImsPhone).setServiceState(eq(ServiceState.STATE_IN_SERVICE));
verify(mImsPhone).setImsRegistered(eq(true));
}
@Test
@SmallTest
public void testImsRegistering() {
// when IMS is registering
mRegistrationCallback.onRegistering(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
// then service state should be OUT_OF_SERVICE and ImsPhone state set to not registered
verify(mImsPhone).setServiceState(eq(ServiceState.STATE_OUT_OF_SERVICE));
verify(mImsPhone).setImsRegistered(eq(false));
}
@Test
@SmallTest
public void testImsDeregistered() {
// when IMS is deregistered
mRegistrationCallback.onDeregistered(new ImsReasonInfo());
// then service state should be OUT_OF_SERVICE and ImsPhone state set to not registered
verify(mImsPhone).setServiceState(eq(ServiceState.STATE_OUT_OF_SERVICE));
verify(mImsPhone).setImsRegistered(eq(false));
}
@Test
@SmallTest
public void testVowifiDisabledOnLte() {
// LTE is registered.
doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_LTE).when(
mImsManager).getRegistrationTech();
assertFalse(mCTUT.isVowifiEnabled());
// enable Voice over LTE
ImsFeature.Capabilities caps = new ImsFeature.Capabilities();
caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
mCapabilityCallback.onCapabilitiesStatusChanged(caps);
waitForHandlerAction(mCTHander, 1000);
// Voice over IWLAN is still disabled
assertFalse(mCTUT.isVowifiEnabled());
}
@Test
@SmallTest
public void testVowifiDisabledOnIwlan() {
// LTE is registered.
doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN).when(
mImsManager).getRegistrationTech();
assertFalse(mCTUT.isVowifiEnabled());
// enable Voice over IWLAN
ImsFeature.Capabilities caps = new ImsFeature.Capabilities();
caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
mCapabilityCallback.onCapabilitiesStatusChanged(caps);
waitForHandlerAction(mCTHander, 1000);
// Voice over IWLAN is enabled
assertTrue(mCTUT.isVowifiEnabled());
}
@Test
@SmallTest
public void testImsFeatureCapabilityChange() {
doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_LTE).when(
mImsManager).getRegistrationTech();
assertFalse(mCTUT.isVolteEnabled());
assertFalse(mCTUT.isVideoCallEnabled());
// enable only Voice
ImsFeature.Capabilities caps = new ImsFeature.Capabilities();
caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
mCapabilityCallback.onCapabilitiesStatusChanged(caps);
waitForHandlerAction(mCTHander, 1000);
assertTrue(mCTUT.isVolteEnabled());
assertFalse(mCTUT.isVideoCallEnabled());
// video call not enabled
verify(mImsPhone, times(0)).notifyForVideoCapabilityChanged(anyBoolean());
verify(mImsPhone, times(1)).onFeatureCapabilityChanged();
// enable video call
ImsFeature.Capabilities capsVideo = new ImsFeature.Capabilities();
capsVideo.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
capsVideo.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO);
mCapabilityCallback.onCapabilitiesStatusChanged(capsVideo);
waitForHandlerAction(mCTHander, 1000);
assertTrue(mCTUT.isVideoCallEnabled());
verify(mImsPhone, times(1)).notifyForVideoCapabilityChanged(eq(true));
}
@Test
@SmallTest
public void testImsMTCall() {
assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
assertFalse(mCTUT.mRingingCall.isRinging());
// mock a MT call
mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY);
verify(mImsPhone, times(1)).notifyNewRingingConnection((Connection) any());
verify(mImsPhone, times(1)).notifyIncomingRing();
assertEquals(PhoneConstants.State.RINGING, mCTUT.getState());
assertTrue(mCTUT.mRingingCall.isRinging());
assertEquals(1, mCTUT.mRingingCall.getConnections().size());
ImsPhoneConnection connection =
(ImsPhoneConnection) mCTUT.mRingingCall.getConnections().get(0);
connection.addListener(mImsPhoneConnectionListener);
}
@Test
@SmallTest
public void testImsMTCallAccept() {
testImsMTCall();
assertTrue(mCTUT.mRingingCall.isRinging());
try {
mCTUT.acceptCall(ImsCallProfile.CALL_TYPE_VOICE);
verify(mImsCall, times(1)).accept(eq(ImsCallProfile
.getCallTypeFromVideoState(ImsCallProfile.CALL_TYPE_VOICE)));
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
assertFalse(mCTUT.mRingingCall.isRinging());
assertEquals(PhoneConstants.State.OFFHOOK, mCTUT.getState());
assertEquals(Call.State.ACTIVE, mCTUT.mForegroundCall.getState());
assertEquals(1, mCTUT.mForegroundCall.getConnections().size());
}
@Test
@SmallTest
public void testImsMTCallReject() {
testImsMTCall();
assertTrue(mCTUT.mRingingCall.isRinging());
try {
mCTUT.rejectCall();
verify(mImsCall, times(1)).reject(eq(ImsReasonInfo.CODE_USER_DECLINE));
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
assertFalse(mCTUT.mRingingCall.isRinging());
assertEquals(0, mCTUT.mRingingCall.getConnections().size());
assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
}
@Test
@SmallTest
public void testImsMTCallAcceptHangUp() {
testImsMTCallAccept();
assertEquals(Call.State.ACTIVE, mCTUT.mForegroundCall.getState());
assertEquals(PhoneConstants.State.OFFHOOK, mCTUT.getState());
try {
mCTUT.hangup(mCTUT.mForegroundCall);
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
assertEquals(Call.State.IDLE, mCTUT.mForegroundCall.getState());
}
@Test
@SmallTest
public void testImsMTCallAcceptHold() {
testImsMTCallAccept();
assertEquals(Call.State.ACTIVE, mCTUT.mForegroundCall.getState());
assertEquals(PhoneConstants.State.OFFHOOK, mCTUT.getState());
// mock a new MT
try {
doReturn(mSecondImsCall).when(mImsManager).takeCall(any(IImsCallSession.class),
any(Bundle.class), any(ImsCall.Listener.class));
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY);
verify(mImsPhone, times(2)).notifyNewRingingConnection((Connection) any());
verify(mImsPhone, times(2)).notifyIncomingRing();
assertEquals(Call.State.ACTIVE, mCTUT.mForegroundCall.getState());
assertEquals(ImsPhoneCall.State.WAITING, mCTUT.mRingingCall.getState());
assertEquals(PhoneConstants.State.RINGING, mCTUT.getState());
//hold the foreground active call, accept the new ringing call
try {
mCTUT.acceptCall(ImsCallProfile.CALL_TYPE_VOICE);
verify(mImsCall, times(1)).hold();
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
waitForMs(100);
assertEquals(Call.State.ACTIVE, mCTUT.mForegroundCall.getState());
assertFalse(mCTUT.mRingingCall.isRinging());
assertEquals(Call.State.HOLDING, mCTUT.mBackgroundCall.getState());
}
/**
* Ensures that the dial method will perform a shared preferences lookup using the correct
* shared preference key to determine the CLIR mode.
*/
@Test
@SmallTest
public void testDialClirMode() {
mCTUT.setSharedPreferenceProxy((Context context) -> {
return mSharedPreferences;
});
ArgumentCaptor<String> mStringCaptor = ArgumentCaptor.forClass(String.class);
doReturn(CommandsInterface.CLIR_INVOCATION).when(mSharedPreferences).getInt(
mStringCaptor.capture(), anyInt());
try {
mCTUT.dial("+17005554141", VideoProfile.STATE_AUDIO_ONLY, null);
} catch (CallStateException cse) {
cse.printStackTrace();
Assert.fail("unexpected exception thrown" + cse.getMessage());
}
// Ensure that the correct key was queried from the shared prefs.
assertEquals("clir_key0", mStringCaptor.getValue());
}
/**
* Ensures for an emergency call that the dial method will default the CLIR to
* {@link CommandsInterface#CLIR_SUPPRESSION}, ensuring the caller's ID is shown.
*/
@Test
@SmallTest
public void testEmergencyDialSuppressClir() {
mCTUT.setSharedPreferenceProxy((Context context) -> {
return mSharedPreferences;
});
// Mock implementation of phone number utils treats everything as an emergency.
mCTUT.setPhoneNumberUtilsProxy((String string) -> {
return true;
});
// Set preference to hide caller ID.
ArgumentCaptor<String> stringCaptor = ArgumentCaptor.forClass(String.class);
doReturn(CommandsInterface.CLIR_INVOCATION).when(mSharedPreferences).getInt(
stringCaptor.capture(), anyInt());
try {
mCTUT.dial("+17005554141", VideoProfile.STATE_AUDIO_ONLY, null);
ArgumentCaptor<ImsCallProfile> profileCaptor = ArgumentCaptor.forClass(
ImsCallProfile.class);
verify(mImsManager, times(1)).makeCall(eq(mImsCallProfile),
eq(new String[]{"+17005554141"}), any());
// Because this is an emergency call, we expect caller id to be visible now.
assertEquals(mImsCallProfile.getCallExtraInt(ImsCallProfile.EXTRA_OIR),
CommandsInterface.CLIR_SUPPRESSION);
} catch (CallStateException cse) {
cse.printStackTrace();
Assert.fail("unexpected exception thrown" + cse.getMessage());
} catch (ImsException ie) {
ie.printStackTrace();
Assert.fail("unexpected exception thrown" + ie.getMessage());
}
}
@FlakyTest
@Ignore
@Test
@SmallTest
public void testImsMOCallDial() {
startOutgoingCall();
//call established
mImsCallListener.onCallProgressing(mSecondImsCall);
assertEquals(Call.State.ALERTING, mCTUT.mForegroundCall.getState());
}
private void startOutgoingCall() {
assertEquals(Call.State.IDLE, mCTUT.mForegroundCall.getState());
assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
try {
mCTUT.dial("+17005554141", ImsCallProfile.CALL_TYPE_VOICE, null);
verify(mImsManager, times(1)).makeCall(eq(mImsCallProfile),
eq(new String[]{"+17005554141"}), (ImsCall.Listener) any());
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
assertEquals(PhoneConstants.State.OFFHOOK, mCTUT.getState());
assertEquals(Call.State.DIALING, mCTUT.mForegroundCall.getState());
}
@FlakyTest
@Ignore
@Test
@SmallTest
public void testImsMTActiveMODial() {
assertEquals(Call.State.IDLE, mCTUT.mForegroundCall.getState());
assertEquals(Call.State.IDLE, mCTUT.mBackgroundCall.getState());
testImsMTCallAccept();
assertEquals(Call.State.ACTIVE, mCTUT.mForegroundCall.getState());
assertEquals(Call.State.IDLE, mCTUT.mBackgroundCall.getState());
try {
mCTUT.dial("+17005554141", ImsCallProfile.CALL_TYPE_VOICE, null);
verify(mImsManager, times(1)).makeCall(eq(mImsCallProfile),
eq(new String[]{"+17005554141"}), (ImsCall.Listener) any());
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
waitForMs(100);
assertEquals(Call.State.DIALING, mCTUT.mForegroundCall.getState());
assertEquals(Call.State.HOLDING, mCTUT.mBackgroundCall.getState());
}
@Test
@SmallTest
public void testImsMOCallHangup() {
testImsMOCallDial();
//hangup before call go to active
try {
mCTUT.hangup(mCTUT.mForegroundCall);
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
assertEquals(Call.State.IDLE, mCTUT.mForegroundCall.getState());
}
@Test
@SmallTest
public void testImsSendDtmf() {
//establish a MT call
testImsMTCallAccept();
mCTUT.sendDtmf(PhoneNumberUtils.PAUSE, null);
//verify trigger sendDtmf to mImsCall
verify(mImsCall, times(1)).sendDtmf(eq(PhoneNumberUtils.PAUSE), (Message) isNull());
// mock a new MT
try {
doReturn(mSecondImsCall).when(mImsManager).takeCall(any(IImsCallSession.class),
any(Bundle.class), any(ImsCall.Listener.class));
mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY);
mCTUT.acceptCall(ImsCallProfile.CALL_TYPE_VOICE);
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
waitForMs(100);
mCTUT.sendDtmf(PhoneNumberUtils.WAIT, null);
//verify trigger sendDtmf to mImsSecondCall
verify(mSecondImsCall, times(1)).sendDtmf(eq(PhoneNumberUtils.WAIT), (Message) isNull());
}
@Test
@SmallTest
public void testReasonCodeRemap() {
assertEquals(ImsReasonInfo.CODE_WIFI_LOST, mCTUT.maybeRemapReasonCode(
new ImsReasonInfo(1, 1, "Wifi signal lost.")));
assertEquals(ImsReasonInfo.CODE_WIFI_LOST, mCTUT.maybeRemapReasonCode(
new ImsReasonInfo(200, 1, "Wifi signal lost.")));
assertEquals(ImsReasonInfo.CODE_ANSWERED_ELSEWHERE,
mCTUT.maybeRemapReasonCode(new ImsReasonInfo(501, 1, "Call answered elsewhere.")));
assertEquals(ImsReasonInfo.CODE_ANSWERED_ELSEWHERE,
mCTUT.maybeRemapReasonCode(new ImsReasonInfo(510, 1, "Call answered elsewhere.")));
assertEquals(90210, mCTUT.maybeRemapReasonCode(new ImsReasonInfo(90210, 1,
"Call answered elsewhere.")));
}
@Test
@SmallTest
public void testDialImsServiceUnavailable() throws ImsException {
doThrow(new ImsException("Test Exception", ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN)).when(
mImsManager).createCallProfile(anyInt(), anyInt());
mCTUT.setRetryTimeout(() -> 0);
assertEquals(Call.State.IDLE, mCTUT.mForegroundCall.getState());
assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
try {
mCTUT.dial("+17005554141", ImsCallProfile.CALL_TYPE_VOICE, null);
} catch (Exception e) {
Assert.fail();
}
// wait for handler to process ImsService connection retry
waitForHandlerAction(mCTHander, 1000); // 1 second timeout
verify(mImsManager, never()).makeCall(nullable(ImsCallProfile.class),
eq(new String[]{"+17005554141"}), nullable(ImsCall.Listener.class));
// Make sure that open is called in ImsPhoneCallTracker when it was first connected and
// again after retry.
verify(mImsManager, times(2)).open(
nullable(MmTelFeature.Listener.class));
}
@FlakyTest
@Ignore
@Test
@SmallTest
public void testTTYImsServiceUnavailable() throws ImsException {
doThrow(new ImsException("Test Exception", ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN)).when(
mImsManager).setUiTTYMode(nullable(Context.class), anyInt(),
nullable(Message.class));
// Remove retry timeout delay
mCTUT.setRetryTimeout(() -> 0); //ms
mCTUT.setUiTTYMode(0, new Message());
// wait for handler to process ImsService connection retry
waitForHandlerAction(mCTHander, 100);
// Make sure that open is called in ImsPhoneCallTracker to re-establish connection to
// ImsService
verify(mImsManager, times(2)).open(
nullable(MmTelFeature.Listener.class));
}
/**
* Test notification of handover from LTE to WIFI and WIFI to LTE and ensure that the expected
* connection events are sent.
*/
@Test
@SmallTest
public void testNotifyHandovers() {
setupCarrierConfig();
//establish a MT call
testImsMTCallAccept();
ImsPhoneConnection connection =
(ImsPhoneConnection) mCTUT.mForegroundCall.getConnections().get(0);
ImsCall call = connection.getImsCall();
// Needs to be a video call to see this signalling.
mImsCallProfile.mCallType = ImsCallProfile.CALL_TYPE_VT;
// First handover from LTE to WIFI; this takes us into a mid-call state.
call.getImsCallSessionListenerProxy().callSessionHandover(call.getCallSession(),
ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN,
new ImsReasonInfo());
// Handover back to LTE.
call.getImsCallSessionListenerProxy().callSessionHandover(call.getCallSession(),
ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN, ServiceState.RIL_RADIO_TECHNOLOGY_LTE,
new ImsReasonInfo());
verify(mImsPhoneConnectionListener).onConnectionEvent(eq(
TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE), isNull());
// Finally hand back to WIFI
call.getImsCallSessionListenerProxy().callSessionHandover(call.getCallSession(),
ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN,
new ImsReasonInfo());
verify(mImsPhoneConnectionListener).onConnectionEvent(eq(
TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_LTE_TO_WIFI), isNull());
}
/**
* Configure carrier config options relevant to the unit test.
*/
public void setupCarrierConfig() {
PersistableBundle bundle = new PersistableBundle();
bundle.putBoolean(CarrierConfigManager.KEY_NOTIFY_HANDOVER_VIDEO_FROM_LTE_TO_WIFI_BOOL,
true);
bundle.putBoolean(CarrierConfigManager.KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL,
true);
bundle.putBoolean(CarrierConfigManager.KEY_NOTIFY_VT_HANDOVER_TO_WIFI_FAILURE_BOOL, true);
mCTUT.updateCarrierConfigCache(bundle);
}
@Test
@SmallTest
public void testLowBatteryDisconnectMidCall() {
assertEquals(DisconnectCause.LOW_BATTERY, mCTUT.getDisconnectCauseFromReasonInfo(
new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_LOW_BATTERY, 0), Call.State.ACTIVE));
assertEquals(DisconnectCause.LOW_BATTERY, mCTUT.getDisconnectCauseFromReasonInfo(
new ImsReasonInfo(ImsReasonInfo.CODE_LOW_BATTERY, 0), Call.State.ACTIVE));
}
@Test
@SmallTest
public void testImsAlternateEmergencyDisconnect() {
assertEquals(DisconnectCause.IMS_SIP_ALTERNATE_EMERGENCY_CALL,
mCTUT.getDisconnectCauseFromReasonInfo(
new ImsReasonInfo(ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL, 0),
Call.State.ACTIVE));
}
@Test
@SmallTest
public void testLowBatteryDisconnectDialing() {
assertEquals(DisconnectCause.DIAL_LOW_BATTERY, mCTUT.getDisconnectCauseFromReasonInfo(
new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_LOW_BATTERY, 0), Call.State.DIALING));
assertEquals(DisconnectCause.DIAL_LOW_BATTERY, mCTUT.getDisconnectCauseFromReasonInfo(
new ImsReasonInfo(ImsReasonInfo.CODE_LOW_BATTERY, 0), Call.State.DIALING));
}
/**
* Tests that no hold tone is played if the call is remotely held and the media direction is
* send/receive (i.e. there is an audio stream present).
*/
@Test
@SmallTest
public void testNoRemoteHoldtone() {
//establish a MT call
testImsMTCallAccept();
ImsPhoneConnection connection = mCTUT.mForegroundCall.getFirstConnection();
ImsCall call = connection.getImsCall();
// Set the media direction to send/receive.
ImsCallProfile callProfile = new ImsCallProfile();
callProfile.mMediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
call.setCallProfile(callProfile);
try {
mCTUT.onCallHoldReceived(call);
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
verify(mImsPhone, never()).startOnHoldTone(nullable(Connection.class));
}
/**
* Verifies that a remote hold tone is played when the call is remotely held and the media
* direction is inactive (i.e. the audio stream is not playing, so we should play the tone).
*/
@Test
@SmallTest
public void testRemoteToneInactive() {
//establish a MT call
testImsMTCallAccept();
ImsPhoneConnection connection = mCTUT.mForegroundCall.getFirstConnection();
ImsCall call = connection.getImsCall();
// Set the media direction to inactive to trigger a hold tone.
ImsCallProfile callProfile = new ImsCallProfile();
callProfile.mMediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_INACTIVE;
call.setCallProfile(callProfile);
try {
mCTUT.onCallHoldReceived(call);
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
verify(mImsPhone, times(1)).startOnHoldTone(nullable(Connection.class));
}
@Test
@SmallTest
public void testRemoteHoldtone() {
// Set carrier config to always play remote hold tone.
mCTUT.setAlwaysPlayRemoteHoldTone(true);
//establish a MT call
testImsMTCallAccept();
ImsPhoneConnection connection = mCTUT.mForegroundCall.getFirstConnection();
ImsCall call = connection.getImsCall();
// Set the media direction to send/receive; normally we don't play a hold tone but the
// carrier config option is set to ensure we will do it in this case.
ImsCallProfile callProfile = new ImsCallProfile();
callProfile.mMediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
call.setCallProfile(callProfile);
try {
mCTUT.onCallHoldReceived(call);
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
verify(mImsPhone, times(1)).startOnHoldTone(nullable(Connection.class));
}
@Test
@SmallTest
public void testCallRestrictedDisconnect() {
doReturn(true).when(mSST.mRestrictedState).isCsRestricted();
assertEquals(DisconnectCause.CS_RESTRICTED, mCTUT.getDisconnectCauseFromReasonInfo(
new ImsReasonInfo(ImsReasonInfo.CODE_UNSPECIFIED, 0), Call.State.ACTIVE));
}
@Test
@SmallTest
public void testCallRestrictedEmergencyDisconnect() {
doReturn(true).when(mSST.mRestrictedState).isCsEmergencyRestricted();
assertEquals(DisconnectCause.CS_RESTRICTED_EMERGENCY,
mCTUT.getDisconnectCauseFromReasonInfo(
new ImsReasonInfo(ImsReasonInfo.CODE_UNSPECIFIED, 0), Call.State.ACTIVE));
}
@Test
@SmallTest
public void testCallRestrictedNormal() {
doReturn(true).when(mSST.mRestrictedState).isCsNormalRestricted();
assertEquals(DisconnectCause.CS_RESTRICTED_NORMAL,
mCTUT.getDisconnectCauseFromReasonInfo(
new ImsReasonInfo(ImsReasonInfo.CODE_UNSPECIFIED, 0), Call.State.ACTIVE));
}
@Test
@SmallTest
public void testSipNotFoundRemap() {
assertEquals(DisconnectCause.INVALID_NUMBER,
mCTUT.getDisconnectCauseFromReasonInfo(
new ImsReasonInfo(ImsReasonInfo.CODE_SIP_NOT_FOUND, 0), Call.State.ACTIVE));
}
@Test
@SmallTest
public void testCantMakeCallWhileRinging() {
testImsMTCall();
try {
mCTUT.dial("6505551212", VideoProfile.STATE_AUDIO_ONLY, new Bundle());
} catch (CallStateException e) {
// We expect a call state exception!
assertEquals(CallStateException.ERROR_CALL_RINGING, e.getError());
return;
}
Assert.fail("Expected CallStateException");
}
@Test
@SmallTest
public void testCantMakeCallWhileDialing() {
startOutgoingCall();
try {
mCTUT.dial("6505551212", VideoProfile.STATE_AUDIO_ONLY, new Bundle());
} catch (CallStateException e) {
// We expect a call state exception!
assertEquals(CallStateException.ERROR_ALREADY_DIALING, e.getError());
return;
}
Assert.fail("Expected CallStateException");
}
@Test
@SmallTest
public void testCantMakeCallTooMany() {
// Place a call.
placeCallAndMakeActive();
// Place another call
placeCallAndMakeActive();
// Finally, dial a third.
try {
mCTUT.dial("6505551212", VideoProfile.STATE_AUDIO_ONLY, new Bundle());
} catch (CallStateException e) {
// We expect a call state exception!
assertEquals(CallStateException.ERROR_TOO_MANY_CALLS, e.getError());
return;
}
Assert.fail("Expected CallStateException");
}
private void placeCallAndMakeActive() {
try {
doAnswer(new Answer<ImsCall>() {
@Override
public ImsCall answer(InvocationOnMock invocation) throws Throwable {
mImsCallListener =
(ImsCall.Listener) invocation.getArguments()[2];
ImsCall imsCall = spy(new ImsCall(mContext, mImsCallProfile));
imsCall.setListener(mImsCallListener);
imsCallMocking(imsCall);
return imsCall;
}
}).when(mImsManager).makeCall(eq(mImsCallProfile), (String[]) any(),
(ImsCall.Listener) any());
} catch (ImsException ie) {
}
ImsPhoneConnection connection = null;
try {
connection = (ImsPhoneConnection) mCTUT.dial("+16505551212",
ImsCallProfile.CALL_TYPE_VOICE, null);
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
if (connection == null) {
Assert.fail("connection is null");
}
ImsCall imsCall = connection.getImsCall();
imsCall.getImsCallSessionListenerProxy().callSessionProgressing(imsCall.getSession(),
new ImsStreamMediaProfile());
imsCall.getImsCallSessionListenerProxy().callSessionStarted(imsCall.getSession(),
new ImsCallProfile());
}
}