blob: 0af95e88373c5e548eaecbcdad672cf8c553bb3d [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 android.net.NetworkStats.DEFAULT_NETWORK_YES;
import static android.net.NetworkStats.METERED_YES;
import static android.net.NetworkStats.ROAMING_NO;
import static android.net.NetworkStats.SET_FOREGROUND;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
import static android.telephony.CarrierConfigManager.ImsVoice.ALERTING_SRVCC_SUPPORT;
import static android.telephony.CarrierConfigManager.ImsVoice.BASIC_SRVCC_SUPPORT;
import static android.telephony.CarrierConfigManager.ImsVoice.MIDCALL_SRVCC_SUPPORT;
import static android.telephony.CarrierConfigManager.ImsVoice.PREALERTING_SRVCC_SUPPORT;
import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_ACTIVE;
import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_ALERTING;
import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_INCOMING;
import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_INCOMING_SETUP;
import static android.telephony.TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED;
import static android.telephony.TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED;
import static android.telephony.TelephonyManager.SRVCC_STATE_HANDOVER_FAILED;
import static android.telephony.TelephonyManager.SRVCC_STATE_HANDOVER_STARTED;
import static android.telephony.emergency.EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE;
import static android.telephony.ims.ImsStreamMediaProfile.DIRECTION_INACTIVE;
import static android.telephony.ims.ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
import static android.telephony.ims.feature.MmTelFeature.IMS_TRAFFIC_DIRECTION_INCOMING;
import static android.telephony.ims.feature.MmTelFeature.IMS_TRAFFIC_DIRECTION_OUTGOING;
import static com.android.testutils.NetworkStatsUtilsKt.assertNetworkStatsEquals;
import static junit.framework.Assert.assertNotNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
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.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.NetworkStats;
import android.net.NetworkStats.Entry;
import android.net.Uri;
import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.os.Bundle;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.telecom.VideoProfile;
import android.telephony.AccessNetworkConstants;
import android.telephony.CarrierConfigManager;
import android.telephony.DisconnectCause;
import android.telephony.PhoneNumberUtils;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsCallProfile;
import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsConferenceState;
import android.telephony.ims.ImsMmTelManager;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ImsStreamMediaProfile;
import android.telephony.ims.RtpHeaderExtensionType;
import android.telephony.ims.SrvccCall;
import android.telephony.ims.aidl.IImsTrafficSessionCallback;
import android.telephony.ims.aidl.ISrvccStartedCallback;
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.FlakyTest;
import com.android.ims.FeatureConnector;
import com.android.ims.ImsCall;
import com.android.ims.ImsConfig;
import com.android.ims.ImsException;
import com.android.ims.ImsManager;
import com.android.ims.internal.ConferenceParticipant;
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.IccCardConstants;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.SrvccConnection;
import com.android.internal.telephony.TelephonyTest;
import com.android.internal.telephony.d2d.RtpTransport;
import com.android.internal.telephony.imsphone.ImsPhoneCallTracker.VtDataUsageProvider;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class ImsPhoneCallTrackerTest extends TelephonyTest {
private ImsPhoneCallTracker mCTUT;
private MmTelFeature.Listener mMmTelListener;
private FeatureConnector.Listener<ImsManager> mConnectorListener;
private ImsMmTelManager.CapabilityCallback mCapabilityCallback;
private ImsCall.Listener mImsCallListener;
private ImsCall mImsCall;
private ImsCall mSecondImsCall;
private ISrvccStartedCallback mSrvccStartedCallback;
private BroadcastReceiver mBroadcastReceiver;
private Bundle mBundle = new Bundle();
private static final int SUB_0 = 0;
@Nullable private VtDataUsageProvider mVtDataUsageProvider;
// Mocked classes
private ArgumentCaptor<Set<RtpHeaderExtensionType>> mRtpHeaderExtensionTypeCaptor;
private FeatureConnector<ImsManager> mMockConnector;
private ImsCallSession mImsCallSession;
private SharedPreferences mSharedPreferences;
private ImsPhoneConnection.Listener mImsPhoneConnectionListener;
private ImsConfig mImsConfig;
private ImsPhoneConnection mImsPhoneConnection;
private INetworkStatsProviderCallback mVtDataUsageProviderCb;
private ImsPhoneCallTracker.ConnectorFactory mConnectorFactory;
private CommandsInterface mMockCi;
private final Executor mExecutor = Runnable::run;
private void imsCallMocking(final ImsCall imsCall) throws Exception {
doAnswer((Answer<Void>) invocation -> {
// trigger the listener on accept call
if (mImsCallListener != null) {
mImsCallListener.onCallStarted(imsCall);
}
return null;
}).when(imsCall).accept(anyInt());
doAnswer((Answer<Void>) invocation -> {
// 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((Answer<Void>) invocation -> {
// 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((Answer<Void>) invocation -> {
if (mImsCallListener != null) {
mImsCallListener.onCallHeld(imsCall);
}
return null;
}).when(imsCall).hold();
doReturn(mExecutor).when(mContext).getMainExecutor();
imsCall.attachSession(mImsCallSession);
doReturn("1").when(mImsCallSession).getCallId();
doReturn(mImsCallProfile).when(mImsCallSession).getCallProfile();
}
@Before
public void setUp() throws Exception {
super.setUp(getClass().getSimpleName());
mRtpHeaderExtensionTypeCaptor = ArgumentCaptor.forClass(Set.class);
mMockConnector = mock(FeatureConnector.class);
mImsCallSession = mock(ImsCallSession.class);
mSharedPreferences = mock(SharedPreferences.class);
mImsConfig = mock(ImsConfig.class);
mVtDataUsageProviderCb = mock(INetworkStatsProviderCallback.class);
mConnectorFactory = mock(ImsPhoneCallTracker.ConnectorFactory.class);
mImsCallProfile.mCallExtras = mBundle;
mImsCall = spy(new ImsCall(mContext, mImsCallProfile));
mSecondImsCall = spy(new ImsCall(mContext, mImsCallProfile));
mImsPhoneConnectionListener = mock(ImsPhoneConnection.Listener.class);
mImsPhoneConnection = mock(ImsPhoneConnection.class);
mMockCi = mock(CommandsInterface.class);
imsCallMocking(mImsCall);
imsCallMocking(mSecondImsCall);
doReturn(ImsFeature.STATE_READY).when(mImsManager).getImsServiceState();
doReturn(mImsCallProfile).when(mImsManager).createCallProfile(anyInt(), anyInt());
mContextFixture.addSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS);
doAnswer(invocation -> {
mMmTelListener = (MmTelFeature.Listener) invocation.getArguments()[0];
return null;
}).when(mImsManager).open(any(), any(), any());
doAnswer((Answer<ImsCall>) invocation -> {
mImsCallListener =
(ImsCall.Listener) invocation.getArguments()[1];
mImsCall.setListener(mImsCallListener);
return mImsCall;
}).when(mImsManager).takeCall(any(), any());
doAnswer((Answer<ImsCall>) invocation -> {
mImsCallListener =
(ImsCall.Listener) invocation.getArguments()[2];
mSecondImsCall.setListener(mImsCallListener);
return mSecondImsCall;
}).when(mImsManager).makeCall(eq(mImsCallProfile), any(), any());
doAnswer(invocation -> {
mCapabilityCallback = (ImsMmTelManager.CapabilityCallback) invocation.getArguments()[0];
return mCapabilityCallback;
}).when(mImsManager).addCapabilitiesCallback(
any(ImsMmTelManager.CapabilityCallback.class), any());
doReturn(mImsConfig).when(mImsManager).getConfigInterface();
doAnswer((Answer<FeatureConnector<ImsManager>>) invocation -> {
mConnectorListener =
(FeatureConnector.Listener<ImsManager>) invocation.getArguments()[3];
return mMockConnector;
}).when(mConnectorFactory).create(any(), anyInt(), anyString(), any(), any());
mCTUT = new ImsPhoneCallTracker(mImsPhone, mConnectorFactory, Runnable::run);
mCTUT.setDataEnabled(true);
final ArgumentCaptor<VtDataUsageProvider> vtDataUsageProviderCaptor =
ArgumentCaptor.forClass(VtDataUsageProvider.class);
verify(mStatsManager).registerNetworkStatsProvider(anyString(),
vtDataUsageProviderCaptor.capture());
mVtDataUsageProvider = vtDataUsageProviderCaptor.getValue();
assertNotNull(mVtDataUsageProvider);
mVtDataUsageProvider.setProviderCallbackBinder(mVtDataUsageProviderCb);
final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
verify(mContext).registerReceiver(receiverCaptor.capture(), any());
mBroadcastReceiver = receiverCaptor.getValue();
assertNotNull(mBroadcastReceiver);
logd("ImsPhoneCallTracker initiated");
processAllMessages();
verify(mMockConnector).connect();
mConnectorListener.connectionReady(mImsManager, SUB_0);
}
@After
public void tearDown() throws Exception {
mCTUT = null;
mMmTelListener = null;
mConnectorListener = null;
mCapabilityCallback = null;
mImsCallListener = null;
mImsCall = null;
mSecondImsCall = null;
mBroadcastReceiver = null;
mBundle = null;
mVtDataUsageProvider = null;
super.tearDown();
}
@Test
@SmallTest
public void testVowifiDisabledOnLte() {
// LTE is registered.
doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_LTE).when(
mImsManager).getRegistrationTech();
assertFalse(mCTUT.isVowifiEnabled());
// enable Voice over LTE
MmTelFeature.MmTelCapabilities caps = new MmTelFeature.MmTelCapabilities();
caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
mCapabilityCallback.onCapabilitiesStatusChanged(caps);
processAllMessages();
// 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
MmTelFeature.MmTelCapabilities caps = new MmTelFeature.MmTelCapabilities();
caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
mCapabilityCallback.onCapabilitiesStatusChanged(caps);
processAllMessages();
// Voice over IWLAN is enabled
assertTrue(mCTUT.isVowifiEnabled());
}
@Test
@SmallTest
public void testImsFeatureCapabilityChange() {
doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_LTE).when(
mImsManager).getRegistrationTech();
assertFalse(mCTUT.isVoiceOverCellularImsEnabled());
assertFalse(mCTUT.isVideoCallEnabled());
// enable only Voice
MmTelFeature.MmTelCapabilities caps = new MmTelFeature.MmTelCapabilities();
caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
mCapabilityCallback.onCapabilitiesStatusChanged(caps);
processAllMessages();
assertTrue(mCTUT.isVoiceOverCellularImsEnabled());
assertFalse(mCTUT.isVideoCallEnabled());
// video call not enabled
verify(mImsPhone, times(0)).notifyForVideoCapabilityChanged(anyBoolean());
verify(mImsPhone, times(1)).onFeatureCapabilityChanged();
// enable video call
MmTelFeature.MmTelCapabilities capsVideo = new MmTelFeature.MmTelCapabilities();
capsVideo.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
capsVideo.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO);
mCapabilityCallback.onCapabilitiesStatusChanged(capsVideo);
processAllMessages();
assertTrue(mCTUT.isVideoCallEnabled());
verify(mImsPhone, times(1)).notifyForVideoCapabilityChanged(eq(true));
}
@Test
@SmallTest
public void testCarrierConfigLoadSubscription() throws Exception {
// Start with there being no subId loaded, so SubscriptionController#isActiveSubId is false
// as part of setup, connectionReady is called, which ends up calling
// updateCarrierConfiguration. Since the carrier config is not report carrier identified
// config, we should not see updateImsServiceConfig called yet.
verify(mImsManager, never()).updateImsServiceConfig();
// Send disconnected indication
mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
// Receive a subscription loaded and IMS connection ready indication.
doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
mContextFixture.getCarrierConfigBundle().putBoolean(
CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
sendCarrierConfigChanged();
// CarrierConfigLoader has signalled that the carrier config has been applied for a specific
// subscription. This will trigger unavailable -> ready indications.
mConnectorListener.connectionReady(mImsManager, SUB_0);
processAllMessages();
verify(mImsManager).updateImsServiceConfig();
}
@Test
@SmallTest
public void testCarrierConfigSentLocked() throws Exception {
// move to ImsService unavailable state.
mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
mContextFixture.getCarrierConfigBundle().putBoolean(
CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
sendCarrierConfigChanged();
// No ImsService connected, so this will cache the config.
verify(mImsManager, never()).updateImsServiceConfig();
// Connect to ImsService, but sim is locked, so ensure we do not send configs yet
doReturn(mIccCard).when(mPhone).getIccCard();
doReturn(IccCardConstants.State.PIN_REQUIRED).when(mIccCard).getState();
mConnectorListener.connectionReady(mImsManager, SUB_0);
processAllMessages();
verify(mImsManager, never()).updateImsServiceConfig();
// Now move to ready and simulate carrier config change in response to SIM state change.
doReturn(IccCardConstants.State.READY).when(mIccCard).getState();
sendCarrierConfigChanged();
verify(mImsManager).updateImsServiceConfig();
}
@Test
@SmallTest
public void testCarrierConfigSentAfterReady() throws Exception {
verify(mImsManager, never()).updateImsServiceConfig();
// Receive a subscription loaded and IMS connection ready indication.
doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
mContextFixture.getCarrierConfigBundle().putBoolean(
CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
// CarrierConfigLoader has signalled that the carrier config has been applied for a specific
// subscription. This will trigger unavailable -> ready indications.
mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
mConnectorListener.connectionReady(mImsManager, SUB_0);
processAllMessages();
// Did not receive carrier config changed yet
verify(mImsManager, never()).updateImsServiceConfig();
sendCarrierConfigChanged();
processAllMessages();
verify(mImsManager).updateImsServiceConfig();
}
@Test
@SmallTest
public void testCarrierConfigSentBeforeReady() throws Exception {
// move to ImsService unavailable state.
mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
mContextFixture.getCarrierConfigBundle().putBoolean(
CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
sendCarrierConfigChanged();
// No ImsService connected, so this will cache the config.
verify(mImsManager, never()).updateImsServiceConfig();
// Connect to ImsService and ensure that the pending carrier config change is processed
// properly.
mConnectorListener.connectionReady(mImsManager, SUB_0);
processAllMessages();
verify(mImsManager).updateImsServiceConfig();
}
@Test
@SmallTest
public void testCarrierConfigSentAfterReadyAndCrash() throws Exception {
verify(mImsManager, never()).updateImsServiceConfig();
// Receive a subscription loaded and IMS connection ready indication.
doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
mContextFixture.getCarrierConfigBundle().putBoolean(
CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
// CarrierConfigLoader has signalled that the carrier config has been applied for a specific
// subscription. This will trigger unavailable -> ready indications.
mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
mConnectorListener.connectionReady(mImsManager, SUB_0);
processAllMessages();
// Did not receive carrier config changed yet
verify(mImsManager, never()).updateImsServiceConfig();
sendCarrierConfigChanged();
processAllMessages();
// ImsService crashes and reconnects
mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
mConnectorListener.connectionReady(mImsManager, SUB_0);
processAllMessages();
verify(mImsManager, times(2)).updateImsServiceConfig();
}
@Test
@SmallTest
public void testCarrierConfigSentBeforeReadyAndCrash() throws Exception {
// move to ImsService unavailable state.
mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
mContextFixture.getCarrierConfigBundle().putBoolean(
CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
sendCarrierConfigChanged();
// No ImsService connected, so this will cache the config.
verify(mImsManager, never()).updateImsServiceConfig();
// Connect to ImsService and then simulate a crash recovery. We should make sure that the
// configs are sent again after recovery.
mConnectorListener.connectionReady(mImsManager, SUB_0);
mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
mConnectorListener.connectionReady(mImsManager, SUB_0);
processAllMessages();
verify(mImsManager, times(2)).updateImsServiceConfig();
}
@Test
@SmallTest
public void testImsMTCall() {
ImsPhoneConnection connection = setupRingingConnection();
assertEquals(android.telecom.Connection.VERIFICATION_STATUS_PASSED,
connection.getNumberVerificationStatus());
}
@Test
@SmallTest
public void testImsMTCallMissed() {
ImsPhoneConnection connection = setupRingingConnection();
mImsCallListener.onCallTerminated(connection.getImsCall(),
new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, 0));
assertEquals(DisconnectCause.INCOMING_MISSED, connection.getDisconnectCause());
}
@Test
@SmallTest
public void testImsMTCallRejected() {
ImsPhoneConnection connection = setupRingingConnection();
connection.onHangupLocal();
mImsCallListener.onCallTerminated(connection.getImsCall(),
new ImsReasonInfo(ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT, 0));
assertEquals(DisconnectCause.INCOMING_REJECTED, connection.getDisconnectCause());
}
@Test
@SmallTest
public void testRejectedElsewhereIsRejected() {
ImsPhoneConnection connection = setupRingingConnection();
mImsCallListener.onCallTerminated(connection.getImsCall(),
new ImsReasonInfo(ImsReasonInfo.CODE_REJECTED_ELSEWHERE, 0));
assertEquals(DisconnectCause.INCOMING_REJECTED, connection.getDisconnectCause());
}
@Test
@SmallTest
public void testRemoteCallDeclineIsRejected() {
ImsPhoneConnection connection = setupRingingConnection();
mImsCallListener.onCallTerminated(connection.getImsCall(),
new ImsReasonInfo(ImsReasonInfo.CODE_REMOTE_CALL_DECLINE, 0));
assertEquals(DisconnectCause.INCOMING_REJECTED, connection.getDisconnectCause());
}
private ImsPhoneConnection setupRingingConnection() {
mImsCallProfile.setCallerNumberVerificationStatus(
ImsCallProfile.VERIFICATION_STATUS_PASSED);
assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
assertFalse(mCTUT.mRingingCall.isRinging());
// mock a MT call
mMmTelListener.onIncomingCall(mock(IImsCallSession.class), null, 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);
assertEquals(android.telecom.Connection.VERIFICATION_STATUS_PASSED,
connection.getNumberVerificationStatus());
return connection;
}
@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 testImsCepOnPeer() throws Exception {
PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
bundle.putBoolean(
CarrierConfigManager.KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_ON_PEER_BOOL, true);
mCTUT.updateCarrierConfigCache(bundle);
testImsMTCallAccept();
doReturn(false).when(mImsCall).isConferenceHost();
doReturn(true).when(mImsCall).isMultiparty();
injectConferenceState();
verify(mImsPhoneConnectionListener).onConferenceParticipantsChanged(any());
}
@Test
@SmallTest
public void testImsNoCepOnPeer() throws Exception {
mCTUT.setSupportCepOnPeer(false);
testImsMTCallAccept();
doReturn(false).when(mImsCall).isConferenceHost();
doReturn(true).when(mImsCall).isMultiparty();
injectConferenceState();
verify(mImsPhoneConnectionListener, never()).onConferenceParticipantsChanged(any());
}
private void injectConferenceState() {
ImsPhoneConnection connection = mCTUT.getConnections().get(0);
connection.addListener(mImsPhoneConnectionListener);
ImsConferenceState state = new ImsConferenceState();
// Yuck
Bundle participant = new Bundle();
participant.putString(ImsConferenceState.USER, "sip:6505551212@fakeims.com");
participant.putString(ImsConferenceState.DISPLAY_TEXT, "yuck");
participant.putString(ImsConferenceState.ENDPOINT, "sip:6505551212@fakeims.com");
participant.putString(ImsConferenceState.STATUS, "connected");
state.mParticipants.put("sip:6505551212@fakeims.com", participant);
mImsCall.conferenceStateUpdated(state);
}
@Test
@SmallTest
public void testImsHoldException() throws Exception {
testImsMTCallAccept();
doThrow(new ImsException()).when(mImsCall).hold();
try {
mCTUT.holdActiveCall();
Assert.fail("No exception thrown");
} catch (Exception e) {
// expected
verify(mImsCall).hold();
}
// After the first hold exception, try holding (successfully) again to make sure that it
// goes through
doNothing().when(mImsCall).hold();
try {
mCTUT.holdActiveCall();
verify(mImsCall, times(2)).hold();
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
}
@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(ImsCall.Listener.class));
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
mMmTelListener.onIncomingCall(mock(IImsCallSession.class), null, 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());
}
processAllMessages();
assertEquals(Call.State.ACTIVE, mCTUT.mForegroundCall.getState());
assertFalse(mCTUT.mRingingCall.isRinging());
assertEquals(Call.State.HOLDING, mCTUT.mBackgroundCall.getState());
}
@Test
@SmallTest
public void testImsMTActiveHoldServiceDisconnect() {
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(ImsCall.Listener.class));
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
mMmTelListener.onIncomingCall(mock(IImsCallSession.class), null, 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());
}
processAllMessages();
assertEquals(Call.State.ACTIVE, mCTUT.mForegroundCall.getState());
assertFalse(mCTUT.mRingingCall.isRinging());
assertEquals(Call.State.HOLDING, mCTUT.mBackgroundCall.getState());
// Now fake the ImsService crashing
mCTUT.hangupAllOrphanedConnections(DisconnectCause.LOST_SIGNAL);
assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
try {
// ensure new calls are not blocked by any lingering state after crash.
mCTUT.checkForDialIssues();
} catch (CallStateException e) {
fail("checkForDialIssues should not generate a CallStateException: " + e.getMessage());
}
}
/**
* 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_sub_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() {
String dialString = "+17005554141";
mCTUT.setSharedPreferenceProxy((Context context) -> {
return mSharedPreferences;
});
doReturn(true).when(mTelephonyManager).isEmergencyNumber(dialString);
// 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(dialString, new ImsPhone.ImsDialArgs.Builder().setIsEmergency(true).build());
ArgumentCaptor<ImsCallProfile> profileCaptor = ArgumentCaptor.forClass(
ImsCallProfile.class);
verify(mImsManager, times(1)).makeCall(eq(mImsCallProfile),
eq(new String[]{dialString}), 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());
}
}
@Test
@SmallTest
public void testImsMOCallDial() {
startOutgoingCall();
//call established
mImsCallListener.onCallProgressing(mSecondImsCall);
processAllMessages();
assertEquals(Call.State.ALERTING, mCTUT.mForegroundCall.getState());
}
@Test
@SmallTest
public void testImsMoCallCrash() {
startOutgoingCall();
// Now fake the ImsService crashing
mCTUT.hangupAllOrphanedConnections(DisconnectCause.LOST_SIGNAL);
processAllMessages();
assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
try {
// ensure new calls are not blocked by any lingering state after crash.
mCTUT.checkForDialIssues();
} catch (CallStateException e) {
fail("checkForDialIssues should not generate a CallStateException: " + e.getMessage());
}
}
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());
}
processAllMessages();
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());
}
processAllMessages();
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(ImsCall.Listener.class));
mMmTelListener.onIncomingCall(mock(IImsCallSession.class), null, Bundle.EMPTY);
mCTUT.acceptCall(ImsCallProfile.CALL_TYPE_VOICE);
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
processAllMessages();
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() {
loadReasonCodeRemap();
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(501, 1, "CALL answered elsewhere.")));
assertEquals(ImsReasonInfo.CODE_ANSWERED_ELSEWHERE,
mCTUT.maybeRemapReasonCode(new ImsReasonInfo(510, 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.")));
}
private void clearCarrierConfig() {
PersistableBundle bundle = new PersistableBundle();
mCTUT.updateCarrierConfigCache(bundle);
}
private void loadReasonCodeRemap() {
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.addReasonCodeRemapping(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, "",
ImsReasonInfo.CODE_SIP_FORBIDDEN);
mCTUT.addReasonCodeRemapping(ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE,
"emergency calls over wifi not allowed in this location",
ImsReasonInfo.CODE_EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE);
mCTUT.addReasonCodeRemapping(ImsReasonInfo.CODE_SIP_FORBIDDEN,
"service not allowed in this location",
ImsReasonInfo.CODE_WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION);
}
private void loadReasonCodeRemapCarrierConfig() {
PersistableBundle bundle = new PersistableBundle();
String[] mappings = new String[] {
// These shall be equivalent to the remappings added in setUp():
"*|Wifi signal lost.|1407",
"501|Call answered elsewhere.|1014",
"510|Call answered elsewhere.|1014",
"510||332",
"352|emergency calls over wifi not allowed in this location|1622",
"332|service not allowed in this location|1623",
};
bundle.putStringArray(CarrierConfigManager.KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY,
mappings);
mCTUT.updateCarrierConfigCache(bundle);
}
@Test
@SmallTest
public void testReasonCodeRemapCarrierConfig() {
clearCarrierConfig();
// The map shall become empty now
assertEquals(510, // ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE
mCTUT.maybeRemapReasonCode(new ImsReasonInfo(510, 1, "Call answered elsewhere.")));
loadReasonCodeRemapCarrierConfig();
testReasonCodeRemap();
testNumericOnlyRemap();
testRemapEmergencyCallsOverWfc();
testRemapWfcNotAvailable();
}
private void loadReasonCodeRemapCarrierConfigWithWildcardMessage() {
PersistableBundle bundle = new PersistableBundle();
String[] mappings = new String[]{
"1014|call completed elsewhere|1014",
"1014|Call Rejected By User|510",
"1014|*|510",
"510|Call completed elsewhere|1014",
};
bundle.putStringArray(CarrierConfigManager.KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY,
mappings);
mCTUT.updateCarrierConfigCache(bundle);
}
@Test
@SmallTest
public void testReasonCodeRemapCarrierConfigWithWildcardMessage() {
clearCarrierConfig();
// The map shall become empty now
loadReasonCodeRemapCarrierConfigWithWildcardMessage();
assertEquals(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, mCTUT.maybeRemapReasonCode(
new ImsReasonInfo(1014, 200, "Call Rejected By User"))); // 1014 -> 510
assertEquals(ImsReasonInfo.CODE_ANSWERED_ELSEWHERE, mCTUT.maybeRemapReasonCode(
new ImsReasonInfo(1014, 200, "Call completed elsewhere"))); // 1014 -> 1014
assertEquals(ImsReasonInfo.CODE_ANSWERED_ELSEWHERE, mCTUT.maybeRemapReasonCode(
new ImsReasonInfo(510, 200,
"Call completed elsewhere by instance urn:gsma:imei:xxx"))); // 510 -> 1014
// Simulate that after SIM swap the new carrier config doesn't have the mapping for 1014
loadReasonCodeRemapCarrierConfig();
assertEquals(ImsReasonInfo.CODE_ANSWERED_ELSEWHERE, mCTUT.maybeRemapReasonCode(
new ImsReasonInfo(1014, 200, "Call Rejected By User"))); // 1014 -> 1014
}
@Test
@SmallTest
public void testDialImsServiceUnavailable() throws ImsException {
doThrow(new ImsException("Test Exception", ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN)).when(
mImsManager).createCallProfile(anyInt(), anyInt());
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();
}
processAllMessages();
// Simulate ImsManager getting reconnected.
mConnectorListener.connectionReady(mImsManager, SUB_0);
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(any(), any(), any());
}
@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));
mCTUT.setUiTTYMode(0, new Message());
processAllMessages();
// Make sure that open is called in ImsPhoneCallTracker to re-establish connection to
// ImsService
verify(mImsManager, times(2)).open(any(), any(), any());
}
@Test
@SmallTest
public void testRewriteOutgoingNumber() {
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) {
}
// Perform a dial string remapping.
PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
bundle.putStringArray(CarrierConfigManager.KEY_DIAL_STRING_REPLACE_STRING_ARRAY,
new String[] {"*55:6505551212"});
ImsPhoneConnection connection = null;
try {
connection = (ImsPhoneConnection) mCTUT.dial("*55",
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");
}
Assert.assertEquals("6505551212", connection.getConvertedNumber());
Assert.assertEquals("*55", connection.getAddress());
}
/**
* Tests carrier requirement to re-map certain dialstrings based on the phones service state.
* Dial strings in a particular roaming state (ex. ROAMING_TYPE_INTERNATIONAL) can be mapped
* to the number. Ideally, dialstrings in different roaming states will be mapped to
* different remappings.
*
* ex.
*
* dialstring --> remapping
*
* 611 --> 123 , *611 --> 123 when ServiceState.ROAMING_TYPE_DOMESTIC
*
* 611 --> 456 , *611 --> 456 when ServiceState.ROAMING_TYPE_INTERNATIONAL
*/
@Test
@MediumTest
public void testRewriteOutgoingNumberBasedOnRoamingState() {
// mock carrier [dialstring]:[remapping]
final String dialString = "611";
final String dialStringStar = "*611";
final String remapping1 = "1111111111";
final String remapping2 = "2222222222";
// Create the re-mappings by getting the mock carrier bundle and inserting string arrays
PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
// insert domestic roaming bundle
bundle.putStringArray(CarrierConfigManager
.KEY_DIAL_STRING_REPLACE_STRING_ARRAY,
new String[]{(dialString + ":" + remapping1),
(dialStringStar + ":" + remapping1)});
// insert international roaming bundle
bundle.putStringArray(CarrierConfigManager
.KEY_INTERNATIONAL_ROAMING_DIAL_STRING_REPLACE_STRING_ARRAY,
new String[]{(dialString + ":" + remapping2),
(dialStringStar + ":" + remapping2)});
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) {
}
// set mock call for helper function CallTracker#shouldPerformInternationalNumberRemapping
doReturn(ServiceState.ROAMING_TYPE_INTERNATIONAL)
.when(mServiceState).getVoiceRoamingType();
// perform a call while service is state in roaming international
ImsPhoneConnection connection = null;
try {
connection = (ImsPhoneConnection) mCTUT.dial(dialString,
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");
}
Assert.assertEquals(dialString, connection.getAddress());
Assert.assertEquals(remapping2, connection.getConvertedNumber());
mCTUT.hangupAllOrphanedConnections(DisconnectCause.NORMAL);
// perform a 2nd call while service state is in roaming international
ImsPhoneConnection connection2 = null;
try {
connection2 = (ImsPhoneConnection) mCTUT.dial(dialStringStar,
ImsCallProfile.CALL_TYPE_VOICE, null);
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
if (connection2 == null) {
Assert.fail("connection is null");
}
Assert.assertEquals(dialStringStar, connection2.getAddress());
Assert.assertEquals(remapping2, connection2.getConvertedNumber());
mCTUT.hangupAllOrphanedConnections(DisconnectCause.NORMAL);
// CHANGE THE SERVICE STATE: international --> domestic
doReturn(ServiceState.ROAMING_TYPE_DOMESTIC)
.when(mServiceState).getVoiceRoamingType();
// perform 3rd call while service state is in roaming DOMESTIC
ImsPhoneConnection connection3 = null;
try {
connection3 = (ImsPhoneConnection) mCTUT.dial(dialString,
ImsCallProfile.CALL_TYPE_VOICE, null);
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
if (connection3 == null) {
Assert.fail("connection is null");
}
Assert.assertEquals(dialString, connection3.getAddress());
Assert.assertEquals(remapping1, connection3.getConvertedNumber());
mCTUT.hangupAllOrphanedConnections(DisconnectCause.NORMAL);
// perform 4th call while service state is in roaming DOMESTIC
ImsPhoneConnection connection4 = null;
try {
connection4 = (ImsPhoneConnection) mCTUT.dial(dialStringStar,
ImsCallProfile.CALL_TYPE_VOICE, null);
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
if (connection4 == null) {
Assert.fail("connection is null");
}
Assert.assertEquals(dialStringStar, connection4.getAddress());
Assert.assertEquals(remapping1, connection4.getConvertedNumber());
mCTUT.hangupAllOrphanedConnections(DisconnectCause.NORMAL);
}
/**
* Tests the edge case where the phone is in ServiceState.ROAMING_TYPE_INTERNATIONAL but the
* Carrier never set the bundle for this ServiceState. Always default to
* CarrierConfigManager.KEY_DIAL_STRING_REPLACE_STRING_ARRAY.
*/
@Test
@SmallTest
public void testRewriteOutgoingNumberInternationalButBundleNotSet() {
// mock carrier [dialstring]:[remapping]
final String dialString = "611";
final String dialStringStar = "*611";
final String remapping1 = "1111111111";
// Create the re-mappings by getting the mock carrier bundle and inserting string arrays
PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
// insert domestic roaming bundle
bundle.putStringArray(CarrierConfigManager
.KEY_DIAL_STRING_REPLACE_STRING_ARRAY,
new String[]{(dialString + ":" + remapping1),
(dialStringStar + ":" + remapping1)});
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) {
}
doReturn(ServiceState.ROAMING_TYPE_INTERNATIONAL)
.when(mServiceState).getVoiceRoamingType();
Assert.assertNotNull(mImsPhone);
Assert.assertNotNull(mImsPhone.getDefaultPhone());
ImsPhoneConnection connection = null;
try {
connection = (ImsPhoneConnection) mCTUT.dial(dialString,
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");
}
// helper function CallTracker#shouldPerformInternationalNumberRemapping early exists since
// the KEY_INTERNATIONAL_ROAMING_DIAL_STRING_REPLACE_STRING_ARRAY bundle is null. Therefore,
// we should never check the service state and default to
// KEY_INTERNATIONAL_ROAMING_DIAL_STRING_REPLACE_STRING_ARRAY bundle
verify(mServiceState, times(0)).getVoiceRoamingType();
Assert.assertEquals(mImsPhone.getDefaultPhone().getServiceState().getVoiceRoamingType(),
ServiceState.ROAMING_TYPE_INTERNATIONAL);
Assert.assertNull(bundle.getStringArray(CarrierConfigManager
.KEY_INTERNATIONAL_ROAMING_DIAL_STRING_REPLACE_STRING_ARRAY));
Assert.assertEquals(dialString, connection.getAddress());
Assert.assertEquals(remapping1, connection.getConvertedNumber());
mCTUT.hangupAllOrphanedConnections(DisconnectCause.NORMAL);
}
/**
* 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(),
TelephonyManager.NETWORK_TYPE_LTE, TelephonyManager.NETWORK_TYPE_IWLAN,
new ImsReasonInfo());
// Handover back to LTE.
call.getImsCallSessionListenerProxy().callSessionHandover(call.getCallSession(),
TelephonyManager.NETWORK_TYPE_IWLAN, TelephonyManager.NETWORK_TYPE_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(),
TelephonyManager.NETWORK_TYPE_LTE, TelephonyManager.NETWORK_TYPE_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));
}
@Test
@SmallTest
public void testSendAnbrQuery() throws Exception {
logd("ImsPhoneCallTracker testSendAnbrQuery");
replaceInstance(Phone.class, "mCi", mPhone, mMockCi);
//establish a MT call
testImsMTCallAccept();
ImsPhoneConnection connection = mCTUT.mForegroundCall.getFirstConnection();
ImsCall imsCall = connection.getImsCall();
imsCall.getImsCallSessionListenerProxy().callSessionSendAnbrQuery(1, 1, 24400);
verify(mMockCi, times(1)).sendAnbrQuery(eq(1), eq(1), eq(24400), any());
// Disconnecting and then Disconnected
mCTUT.hangup(connection);
mImsCallListener.onCallTerminated(imsCall,
new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0));
}
@Test
@SmallTest
public void testTriggerNotifyAnbr() throws Exception {
logd("ImsPhoneCallTracker testTriggerNotifyAnbr");
testImsMTCallAccept();
ImsPhoneConnection connection = mCTUT.mForegroundCall.getFirstConnection();
ImsCall imsCall = connection.getImsCall();
mCTUT.triggerNotifyAnbr(1, 1, 24400);
verify(mImsCall, times(1)).callSessionNotifyAnbr(eq(1), eq(1), eq(24400));
// Disconnecting and then Disconnected
mCTUT.hangup(connection);
mImsCallListener.onCallTerminated(imsCall,
new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0));
}
/**
* 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() {
PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
bundle.putBoolean(CarrierConfigManager.KEY_ALLOW_HOLD_VIDEO_CALL_BOOL, true);
mCTUT.updateCarrierConfigCache(bundle);
// 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");
}
@Test
@SmallTest
public void testMergeComplete() {
boolean[] result = new boolean[1];
// Place a call.
ImsPhoneConnection connection = placeCallAndMakeActive();
connection.addListener(new Connection.ListenerBase() {
@Override
public void onConnectionEvent(String event, Bundle extras) {
result[0] = android.telecom.Connection.EVENT_MERGE_COMPLETE.equals(event);
}
});
ImsCall call = connection.getImsCall();
call.getListener().onCallMerged(call, null, false);
assertTrue(result[0]);
}
@Test
@SmallTest
public void testNumericOnlyRemap() {
loadReasonCodeRemap();
assertEquals(ImsReasonInfo.CODE_SIP_FORBIDDEN, mCTUT.maybeRemapReasonCode(
new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, 0)));
assertEquals(ImsReasonInfo.CODE_SIP_FORBIDDEN, mCTUT.maybeRemapReasonCode(
new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, 0, "")));
}
@Test
@SmallTest
public void testRemapEmergencyCallsOverWfc() {
loadReasonCodeRemap();
assertEquals(ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE,
mCTUT.maybeRemapReasonCode(
new ImsReasonInfo(ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE, 0)));
assertEquals(ImsReasonInfo.CODE_EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE,
mCTUT.maybeRemapReasonCode(
new ImsReasonInfo(ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE, 0,
"emergency calls over wifi not allowed in this location")));
assertEquals(ImsReasonInfo.CODE_EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE,
mCTUT.maybeRemapReasonCode(
new ImsReasonInfo(ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE, 0,
"EMERGENCY calls over wifi not allowed in this location")));
}
@Test
@SmallTest
public void testRemapWfcNotAvailable() {
loadReasonCodeRemap();
assertEquals(ImsReasonInfo.CODE_SIP_FORBIDDEN,
mCTUT.maybeRemapReasonCode(
new ImsReasonInfo(ImsReasonInfo.CODE_SIP_FORBIDDEN, 0)));
assertEquals(ImsReasonInfo.CODE_WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION,
mCTUT.maybeRemapReasonCode(
new ImsReasonInfo(ImsReasonInfo.CODE_SIP_FORBIDDEN, 0,
"Service not allowed in this location")));
assertEquals(ImsReasonInfo.CODE_WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION,
mCTUT.maybeRemapReasonCode(
new ImsReasonInfo(ImsReasonInfo.CODE_SIP_FORBIDDEN, 0,
"SERVICE not allowed in this location")));
}
@Test
@SmallTest
public void testNoHoldErrorMessageWhenCallDisconnected() {
when(mImsPhoneConnection.getImsCall()).thenReturn(mImsCall);
mCTUT.getConnections().add(mImsPhoneConnection);
when(mImsPhoneConnection.getState()).thenReturn(ImsPhoneCall.State.DISCONNECTED);
final ImsReasonInfo info = new ImsReasonInfo(ImsReasonInfo.CODE_UNSPECIFIED,
ImsReasonInfo.CODE_UNSPECIFIED, null);
mCTUT.getImsCallListener().onCallHoldFailed(mImsPhoneConnection.getImsCall(), info);
verify(mImsPhoneConnection, never()).onConnectionEvent(
eq(android.telecom.Connection.EVENT_CALL_HOLD_FAILED), any());
}
@Test
@SmallTest
public void testVtDataUsageProvider() throws RemoteException {
mVtDataUsageProvider.onRequestStatsUpdate(11);
// Verify that requestStatsUpdate triggers onStatsUpdated, where the initial token should
// be reported with current stats.
assertVtDataUsageUpdated(0, 0, 0);
// Establish a MT call.
testImsMTCallAccept();
final ImsPhoneConnection connection = mCTUT.mForegroundCall.getFirstConnection();
final ImsCall call = connection.getImsCall();
mCTUT.updateVtDataUsage(call, 51);
// Make another request, and verify stats updated accordingly, with previously issued token.
reset(mVtDataUsageProviderCb);
mVtDataUsageProvider.onRequestStatsUpdate(13);
assertVtDataUsageUpdated(11, 25, 25);
// Update accumulated data usage twice. updateVtDataUsage takes accumulated stats from
// boot up.
reset(mVtDataUsageProviderCb);
mCTUT.updateVtDataUsage(call, 70);
mCTUT.updateVtDataUsage(call, 91);
verify(mVtDataUsageProviderCb, never()).notifyStatsUpdated(anyInt(), any(), any());
// Verify that diff stats from last update is reported accordingly.
mVtDataUsageProvider.onRequestStatsUpdate(13);
// Rounding error occurs so (70-51)/2 + (91-70)/2 = 19 is expected for both direction.
assertVtDataUsageUpdated(13, 19, 19);
}
@Test
@SmallTest
public void testEndRingbackOnSrvcc() throws RemoteException {
mSecondImsCall.getCallProfile().mMediaProfile = new ImsStreamMediaProfile();
mSecondImsCall.getCallProfile().mMediaProfile.mAudioDirection =
ImsStreamMediaProfile.DIRECTION_INACTIVE;
startOutgoingCall();
mImsCallListener.onCallProgressing(mSecondImsCall);
assertTrue(mCTUT.mForegroundCall.isRingbackTonePlaying());
// Move the connection to the handover state.
mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED);
assertFalse(mCTUT.mForegroundCall.isRingbackTonePlaying());
}
@Test
@SmallTest
public void testClearHoldSwapStateOnSrvcc() throws Exception {
// Answer an incoming call
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("set active, unexpected exception thrown" + ex.getMessage());
}
assertEquals(Call.State.ACTIVE, mCTUT.mForegroundCall.getState());
// Hold the call
doNothing().when(mImsCall).hold();
try {
mCTUT.holdActiveCall();
assertTrue(mCTUT.isHoldOrSwapInProgress());
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail("hold, unexpected exception thrown" + ex.getMessage());
}
// Move the connection to the handover state.
mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED);
// Ensure we are no longer tracking hold.
assertFalse(mCTUT.isHoldOrSwapInProgress());
}
@Test
@SmallTest
public void testHangupHandoverCall() throws RemoteException {
doReturn("1").when(mImsCallSession).getCallId();
assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
assertFalse(mCTUT.mRingingCall.isRinging());
// mock a MT call
mMmTelListener.onIncomingCall(mock(IImsCallSession.class), null, 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);
// Move the connection to the handover state.
mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED);
assertEquals(1, mCTUT.mHandoverCall.getConnections().size());
// No need to go through all the rigamarole of the mocked termination we normally do; we
// can confirm the hangup gets processed without all that.
doNothing().when(mImsCall).terminate(anyInt());
try {
mCTUT.hangup(mCTUT.mHandoverCall);
} catch (CallStateException e) {
Assert.fail("CallStateException not expected");
}
assertEquals(DisconnectCause.LOCAL, connection.getDisconnectCause());
}
/**
* Verifies that the {@link ImsPhoneCallTracker#getState()} goes to IDLE when an SRVCC takes
* place.
* @throws RemoteException
*/
@Test
@SmallTest
public void testTrackerStateOnHandover() throws RemoteException {
doReturn("1").when(mImsCallSession).getCallId();
assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
assertFalse(mCTUT.mRingingCall.isRinging());
// mock a MT call
mMmTelListener.onIncomingCall(mock(IImsCallSession.class), null, 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);
// Move the connection to the handover state.
mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED);
assertEquals(1, mCTUT.mHandoverCall.getConnections().size());
// Make sure the tracker states it's idle.
assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
}
/**
* Ensures when both RTP and SDP is supported that we register the expected header extension
* types.
* @throws Exception
*/
@Test
@SmallTest
public void testConfigureRtpHeaderExtensionTypes() throws Exception {
mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
mContextFixture.getCarrierConfigBundle().putBoolean(
CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL,
true);
mContextFixture.getCarrierConfigBundle().putBoolean(
CarrierConfigManager.KEY_SUPPORTS_SDP_NEGOTIATION_OF_D2D_RTP_HEADER_EXTENSIONS_BOOL,
true);
sendCarrierConfigChanged();
ImsPhoneCallTracker.Config config = new ImsPhoneCallTracker.Config();
config.isD2DCommunicationSupported = true;
mCTUT.setConfig(config);
mConnectorListener.connectionReady(mImsManager, SUB_0);
// Expect to get offered header extensions since d2d is supported.
verify(mImsManager).setOfferedRtpHeaderExtensionTypes(
mRtpHeaderExtensionTypeCaptor.capture());
Set<RtpHeaderExtensionType> types = mRtpHeaderExtensionTypeCaptor.getValue();
assertEquals(2, types.size());
assertTrue(types.contains(RtpTransport.CALL_STATE_RTP_HEADER_EXTENSION_TYPE));
assertTrue(types.contains(RtpTransport.DEVICE_STATE_RTP_HEADER_EXTENSION_TYPE));
}
/**
* Ensures when SDP is not supported (by RTP is) we don't register any extensions.
* @throws Exception
*/
@Test
@SmallTest
public void testRtpButNoSdp() throws Exception {
mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
mContextFixture.getCarrierConfigBundle().putBoolean(
CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL,
true);
mContextFixture.getCarrierConfigBundle().putBoolean(
CarrierConfigManager.KEY_SUPPORTS_SDP_NEGOTIATION_OF_D2D_RTP_HEADER_EXTENSIONS_BOOL,
false);
sendCarrierConfigChanged();
ImsPhoneCallTracker.Config config = new ImsPhoneCallTracker.Config();
config.isD2DCommunicationSupported = true;
mCTUT.setConfig(config);
mConnectorListener.connectionReady(mImsManager, SUB_0);
// Expect to get offered header extensions since d2d is supported.
verify(mImsManager).setOfferedRtpHeaderExtensionTypes(
mRtpHeaderExtensionTypeCaptor.capture());
Set<RtpHeaderExtensionType> types = mRtpHeaderExtensionTypeCaptor.getValue();
assertEquals(0, types.size());
}
/**
* Ensures when D2D communication is not supported that we don't register the D2D RTP header
* extension types.
* @throws Exception
*/
@Test
@SmallTest
public void testDontConfigureRtpHeaderExtensionTypes() throws Exception {
mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
sendCarrierConfigChanged();
ImsPhoneCallTracker.Config config = new ImsPhoneCallTracker.Config();
config.isD2DCommunicationSupported = false;
mCTUT.setConfig(config);
mConnectorListener.connectionReady(mImsManager, SUB_0);
// Expect no offered header extensions since d2d is not supported.
verify(mImsManager, never()).setOfferedRtpHeaderExtensionTypes(any());
}
@Test
@SmallTest
public void testCleanupAndRemoveConnection() throws Exception {
ImsPhoneConnection conn = placeCall();
assertEquals(1, mCTUT.getConnections().size());
assertNotNull(mCTUT.getPendingMO());
assertEquals(Call.State.DIALING, mCTUT.mForegroundCall.getState());
assertEquals(PhoneConstants.State.OFFHOOK, mCTUT.getState());
mCTUT.cleanupAndRemoveConnection(conn);
assertEquals(0, mCTUT.getConnections().size());
assertNull(mCTUT.getPendingMO());
assertEquals(Call.State.IDLE, mCTUT.mForegroundCall.getState());
assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
}
@Test
@SmallTest
public void testCallSessionUpdatedAfterSrvccCompleted() throws RemoteException {
startOutgoingCall();
// Move the connection to the handover state.
mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED);
try {
// When trigger CallSessionUpdated after Srvcc completes, checking no exception.
mImsCallListener.onCallUpdated(mSecondImsCall);
} catch (Exception ex) {
Assert.fail("unexpected exception thrown" + ex.getMessage());
}
}
/**
* Tests the case where a dialed call has not yet moved beyond the "pending MO" phase, but the
* user then disconnects. In such a case we need to ensure that the pending MO reference is
* cleared so that another call can be placed.
*/
@Test
@SmallTest
public void testCallDisconnectBeforeActive() {
ImsPhoneConnection connection = placeCall();
assertEquals(1, mCTUT.getConnections().size());
// Call is the pending MO right now.
assertEquals(connection, mCTUT.getPendingMO());
assertEquals(Call.State.DIALING, mCTUT.mForegroundCall.getState());
assertEquals(PhoneConstants.State.OFFHOOK, mCTUT.getState());
mImsCallListener.onCallTerminated(connection.getImsCall(),
new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, 0));
// Make sure pending MO got nulled out.
assertNull(mCTUT.getPendingMO());
// Try making another call; it should not fail.
ImsPhoneConnection connection2 = placeCall();
}
@Test
@SmallTest
public void testConvertToSrvccConnectionInfoNotSupported() throws Exception {
// setup ImsPhoneCallTracker's mConnections
ImsPhoneConnection activeMO = getImsPhoneConnection(Call.State.ACTIVE, "1234", false);
ImsPhoneConnection heldMT = getImsPhoneConnection(Call.State.HOLDING, "5678", true);
ArrayList<ImsPhoneConnection> connections = new ArrayList<ImsPhoneConnection>();
replaceInstance(ImsPhoneCallTracker.class, "mConnections", mCTUT, connections);
connections.add(activeMO);
connections.add(heldMT);
ImsCallProfile activeProfile = getImsCallProfileForSrvccSync("activeCall", activeMO, false);
ImsCallProfile heldProfile = getImsCallProfileForSrvccSync("heldCall", heldMT, false);
// setup the response of notifySrvccStarted
List<SrvccCall> profiles = new ArrayList<>();
SrvccConnection[] srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
assertNull(srvccConnections);
// active call
SrvccCall srvccProfile = new SrvccCall(
"activeCall", PRECISE_CALL_STATE_ACTIVE, activeProfile);
profiles.add(srvccProfile);
PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
bundle.putIntArray(
CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
new int[] {});
mCTUT.updateCarrierConfigCache(bundle);
srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
assertNull(srvccConnections);
}
@Test
@SmallTest
public void testConvertToSrvccConnectionInfoBasicSrvcc() throws Exception {
// setup ImsPhoneCallTracker's mConnections
ImsPhoneConnection activeMO = getImsPhoneConnection(Call.State.ACTIVE, "1234", false);
ImsPhoneConnection heldMT = getImsPhoneConnection(Call.State.HOLDING, "5678", true);
ArrayList<ImsPhoneConnection> connections = new ArrayList<ImsPhoneConnection>();
replaceInstance(ImsPhoneCallTracker.class, "mConnections", mCTUT, connections);
connections.add(activeMO);
connections.add(heldMT);
ImsCallProfile activeProfile = getImsCallProfileForSrvccSync("activeCall", activeMO, false);
ImsCallProfile heldProfile = getImsCallProfileForSrvccSync("heldCall", heldMT, false);
// setup the response of notifySrvccStarted
List<SrvccCall> profiles = new ArrayList<>();
// active call
SrvccCall srvccProfile = new SrvccCall(
"activeCall", PRECISE_CALL_STATE_ACTIVE, activeProfile);
profiles.add(srvccProfile);
PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
bundle.putIntArray(
CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
new int[] {
BASIC_SRVCC_SUPPORT,
});
mCTUT.updateCarrierConfigCache(bundle);
SrvccConnection[] srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
assertNotNull(srvccConnections);
assertTrue(srvccConnections.length == 1);
assertTrue(srvccConnections[0].getState() == Call.State.ACTIVE);
assertEquals("1234", srvccConnections[0].getNumber());
}
@Test
@SmallTest
public void testConvertToSrvccConnectionInfoMoAlerting() throws Exception {
// setup ImsPhoneCallTracker's mConnections
ImsPhoneConnection alertingMO = getImsPhoneConnection(Call.State.ALERTING, "1234", false);
ArrayList<ImsPhoneConnection> connections = new ArrayList<ImsPhoneConnection>();
replaceInstance(ImsPhoneCallTracker.class, "mConnections", mCTUT, connections);
connections.add(alertingMO);
ImsCallProfile alertingProfile = getImsCallProfileForSrvccSync("alertingCall", null, true);
// setup the response of notifySrvccStarted
List<SrvccCall> profiles = new ArrayList<>();
// alerting call, with local ringback tone
SrvccCall srvccProfile = new SrvccCall(
"alertingCall", PRECISE_CALL_STATE_ALERTING, alertingProfile);
profiles.add(srvccProfile);
PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
bundle.putIntArray(
CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
new int[] {
BASIC_SRVCC_SUPPORT,
});
mCTUT.updateCarrierConfigCache(bundle);
SrvccConnection[] srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
assertNull(srvccConnections);
bundle = mContextFixture.getCarrierConfigBundle();
bundle.putIntArray(
CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
new int[] {
BASIC_SRVCC_SUPPORT,
ALERTING_SRVCC_SUPPORT,
});
mCTUT.updateCarrierConfigCache(bundle);
srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
assertNotNull(srvccConnections);
assertTrue(srvccConnections.length == 1);
assertTrue(srvccConnections[0].getState() == Call.State.ALERTING);
assertTrue(srvccConnections[0].getRingbackToneType() == SrvccConnection.TONE_LOCAL);
profiles.clear();
// alerting call, with network ringback tone
alertingProfile = getImsCallProfileForSrvccSync("alertingCall", null, false);
srvccProfile = new SrvccCall(
"alertingCall", PRECISE_CALL_STATE_ALERTING, alertingProfile);
profiles.add(srvccProfile);
srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
assertNotNull(srvccConnections);
assertTrue(srvccConnections.length == 1);
assertTrue(srvccConnections[0].getState() == Call.State.ALERTING);
assertTrue(srvccConnections[0].getRingbackToneType() == SrvccConnection.TONE_NETWORK);
}
@Test
@SmallTest
public void testConvertToSrvccConnectionInfoMtAlerting() throws Exception {
// setup ImsPhoneCallTracker's mConnections
ImsPhoneConnection alertingMT = getImsPhoneConnection(Call.State.INCOMING, "1234", false);
ArrayList<ImsPhoneConnection> connections = new ArrayList<ImsPhoneConnection>();
replaceInstance(ImsPhoneCallTracker.class, "mConnections", mCTUT, connections);
connections.add(alertingMT);
ImsCallProfile incomingProfile =
getImsCallProfileForSrvccSync("incomingCall", alertingMT, false);
// setup the response of notifySrvccStarted
List<SrvccCall> profiles = new ArrayList<>();
SrvccCall srvccProfile = new SrvccCall(
"incomingCall", PRECISE_CALL_STATE_INCOMING, incomingProfile);
profiles.add(srvccProfile);
PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
bundle.putIntArray(
CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
new int[] {
BASIC_SRVCC_SUPPORT,
});
mCTUT.updateCarrierConfigCache(bundle);
SrvccConnection[] srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
assertNull(srvccConnections);
bundle = mContextFixture.getCarrierConfigBundle();
bundle.putIntArray(
CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
new int[] {
ALERTING_SRVCC_SUPPORT,
});
mCTUT.updateCarrierConfigCache(bundle);
srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
assertNotNull(srvccConnections);
assertTrue(srvccConnections.length == 1);
assertTrue(srvccConnections[0].getState() == Call.State.INCOMING);
}
@Test
@SmallTest
public void testConvertToSrvccConnectionInfoMtPreAlerting() throws Exception {
// setup the response of notifySrvccStarted
List<SrvccCall> profiles = new ArrayList<>();
ImsCallProfile incomingProfile = getImsCallProfileForSrvccSync("incomingCall", null, false);
SrvccCall srvccProfile = new SrvccCall(
"incomingCallSetup", PRECISE_CALL_STATE_INCOMING_SETUP, incomingProfile);
profiles.add(srvccProfile);
PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
bundle.putIntArray(
CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
new int[] {
BASIC_SRVCC_SUPPORT,
ALERTING_SRVCC_SUPPORT,
});
mCTUT.updateCarrierConfigCache(bundle);
SrvccConnection[] srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
assertNull(srvccConnections);
bundle = mContextFixture.getCarrierConfigBundle();
bundle.putIntArray(
CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
new int[] {
BASIC_SRVCC_SUPPORT,
ALERTING_SRVCC_SUPPORT,
PREALERTING_SRVCC_SUPPORT,
});
mCTUT.updateCarrierConfigCache(bundle);
srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
assertNotNull(srvccConnections);
assertTrue(srvccConnections.length == 1);
assertTrue(srvccConnections[0].getState() == Call.State.INCOMING);
assertTrue(srvccConnections[0].getSubState() == SrvccConnection.SUBSTATE_PREALERTING);
}
@Test
@SmallTest
public void testNotifySrvccStateStarted() throws Exception {
PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
bundle.putIntArray(
CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
new int[] {
BASIC_SRVCC_SUPPORT,
});
mCTUT.updateCarrierConfigCache(bundle);
mSrvccStartedCallback = null;
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
mSrvccStartedCallback = (ISrvccStartedCallback) invocation.getArguments()[0];
return null;
}
}).when(mImsManager).notifySrvccStarted(any(ISrvccStartedCallback.class));
verify(mImsManager, times(0)).notifySrvccStarted(any());
mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_STARTED);
verify(mImsManager, times(1)).notifySrvccStarted(any());
assertNotNull(mSrvccStartedCallback);
// setup ImsPhoneCallTracker's mConnections
ImsPhoneConnection activeMO = getImsPhoneConnection(Call.State.ACTIVE, "1234", false);
ArrayList<ImsPhoneConnection> connections = new ArrayList<ImsPhoneConnection>();
replaceInstance(ImsPhoneCallTracker.class, "mConnections", mCTUT, connections);
connections.add(activeMO);
ImsCallProfile activeProfile = getImsCallProfileForSrvccSync("activeCall", activeMO, false);
// setup the response of notifySrvccStarted
List<SrvccCall> profiles = new ArrayList<>();
// active call
SrvccCall srvccProfile = new SrvccCall(
"activeCall", PRECISE_CALL_STATE_ACTIVE, activeProfile);
profiles.add(srvccProfile);
mSrvccStartedCallback.onSrvccCallNotified(profiles);
SrvccConnection[] srvccConnections = mSimulatedCommands.getSrvccConnections();
assertNotNull(srvccConnections);
assertTrue(srvccConnections.length == 1);
assertTrue(srvccConnections[0].getState() == Call.State.ACTIVE);
assertEquals("1234", srvccConnections[0].getNumber());
}
@Test
@SmallTest
public void testNotifySrvccStateFailed() throws Exception {
verify(mImsManager, times(0)).notifySrvccFailed();
mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_FAILED);
verify(mImsManager, times(1)).notifySrvccFailed();
}
@Test
@SmallTest
public void testNotifySrvccStateCanceled() throws Exception {
verify(mImsManager, times(0)).notifySrvccCanceled();
mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_CANCELED);
verify(mImsManager, times(1)).notifySrvccCanceled();
}
@Test
@SmallTest
public void testNotifySrvccStateCompleted() throws Exception {
verify(mImsManager, times(0)).notifySrvccCompleted();
mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED);
verify(mImsManager, times(1)).notifySrvccCompleted();
}
@Test
@SmallTest
public void testConvertToSrvccConnectionInfoConferenceCall() throws Exception {
// setup ImsPhoneCallTracker's mConnections
ImsPhoneConnection activeMO = getImsPhoneConnection(Call.State.ACTIVE, "1234", false);
ArrayList<ImsPhoneConnection> connections = new ArrayList<ImsPhoneConnection>();
replaceInstance(ImsPhoneCallTracker.class, "mConnections", mCTUT, connections);
connections.add(activeMO);
List<ConferenceParticipant> participants = new ArrayList<ConferenceParticipant>();
participants.add(new ConferenceParticipant(Uri.parse("tel:1234"), "", null,
android.telecom.Connection.STATE_ACTIVE,
android.telecom.Call.Details.DIRECTION_INCOMING));
participants.add(new ConferenceParticipant(Uri.parse("tel:5678"), "", null,
android.telecom.Connection.STATE_ACTIVE,
android.telecom.Call.Details.DIRECTION_OUTGOING));
ImsCallProfile activeProfile = getImsCallProfileForSrvccSync("activeCall",
activeMO, false, participants);
// setup the response of notifySrvccStarted
List<SrvccCall> profiles = new ArrayList<>();
SrvccConnection[] srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
assertNull(srvccConnections);
// active call
SrvccCall srvccProfile = new SrvccCall(
"activeCall", PRECISE_CALL_STATE_ACTIVE, activeProfile);
profiles.add(srvccProfile);
PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
bundle.putIntArray(
CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
new int[] {
BASIC_SRVCC_SUPPORT,
});
mCTUT.updateCarrierConfigCache(bundle);
srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
assertNotNull(srvccConnections);
assertTrue(srvccConnections.length == 1);
assertTrue(srvccConnections[0].getState() == Call.State.ACTIVE);
assertFalse(srvccConnections[0].isMultiParty());
assertEquals("1234", srvccConnections[0].getNumber());
bundle = mContextFixture.getCarrierConfigBundle();
bundle.putIntArray(
CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
new int[] {
BASIC_SRVCC_SUPPORT,
MIDCALL_SRVCC_SUPPORT
});
mCTUT.updateCarrierConfigCache(bundle);
srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
assertNotNull(srvccConnections);
assertTrue(srvccConnections.length == 2);
assertTrue(srvccConnections[0].getState() == Call.State.ACTIVE);
assertTrue(srvccConnections[0].isMultiParty());
assertTrue(srvccConnections[0].isIncoming());
assertEquals("1234", srvccConnections[0].getNumber());
assertTrue(srvccConnections[1].getState() == Call.State.ACTIVE);
assertTrue(srvccConnections[1].isMultiParty());
assertFalse(srvccConnections[1].isIncoming());
assertEquals("5678", srvccConnections[1].getNumber());
}
/**
* Verifies that the expected access network tech and IMS features are notified
* to ImsPhone when capabilities are changed.
*/
@Test
@SmallTest
public void testUpdateImsRegistrationInfo() {
// LTE is registered.
doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_LTE).when(
mImsManager).getRegistrationTech();
// enable Voice and Video
MmTelFeature.MmTelCapabilities caps = new MmTelFeature.MmTelCapabilities();
caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO);
mCapabilityCallback.onCapabilitiesStatusChanged(caps);
processAllMessages();
verify(mImsPhone, times(1)).updateImsRegistrationInfo(
eq(CommandsInterface.IMS_MMTEL_CAPABILITY_VOICE
| CommandsInterface.IMS_MMTEL_CAPABILITY_VIDEO));
// enable SMS
caps = new MmTelFeature.MmTelCapabilities();
caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS);
mCapabilityCallback.onCapabilitiesStatusChanged(caps);
processAllMessages();
verify(mImsPhone, times(1)).updateImsRegistrationInfo(
eq(CommandsInterface.IMS_MMTEL_CAPABILITY_SMS));
}
@Test
@SmallTest
public void testDomainSelectionAlternateService() {
startOutgoingCall();
ImsPhoneConnection c = mCTUT.mForegroundCall.getFirstConnection();
mImsCallProfile.setEmergencyServiceCategories(EMERGENCY_SERVICE_CATEGORY_AMBULANCE);
mImsCallListener.onCallStartFailed(mSecondImsCall,
new ImsReasonInfo(ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL, -1));
processAllMessages();
EmergencyNumber emergencyNumber = c.getEmergencyNumberInfo();
assertNotNull(emergencyNumber);
assertEquals(EMERGENCY_SERVICE_CATEGORY_AMBULANCE,
emergencyNumber.getEmergencyServiceCategoryBitmask());
}
@Test
public void testUpdateImsCallStatusIncoming() throws Exception {
// Incoming call
ImsPhoneConnection connection = setupRingingConnection();
verify(mImsPhone, times(1)).updateImsCallStatus(any(), any());
// Disconnect the call
mImsCallListener.onCallTerminated(connection.getImsCall(),
new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, 0));
verify(mImsPhone, times(2)).updateImsCallStatus(any(), any());
}
@Test
public void testUpdateImsCallStatus() throws Exception {
// Dialing
ImsPhoneConnection connection = placeCall();
verify(mImsPhone, times(1)).updateImsCallStatus(any(), any());
// Alerting
ImsCall imsCall = connection.getImsCall();
imsCall.getImsCallSessionListenerProxy().callSessionProgressing(imsCall.getSession(),
new ImsStreamMediaProfile());
verify(mImsPhone, times(2)).updateImsCallStatus(any(), any());
// Active
imsCall.getImsCallSessionListenerProxy().callSessionStarted(imsCall.getSession(),
new ImsCallProfile());
verify(mImsPhone, times(3)).updateImsCallStatus(any(), any());
// Held by remote
mCTUT.onCallHoldReceived(imsCall);
verify(mImsPhone, times(4)).updateImsCallStatus(any(), any());
// Resumed by remote
mImsCallListener.onCallResumeReceived(imsCall);
verify(mImsPhone, times(5)).updateImsCallStatus(any(), any());
// Disconnecting and then Disconnected
mCTUT.hangup(connection);
mImsCallListener.onCallTerminated(imsCall,
new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0));
verify(mImsPhone, times(7)).updateImsCallStatus(any(), any());
}
@Test
public void testUpdateImsCallStatusSrvccCompleted() throws Exception {
// Incoming call
setupRingingConnection();
verify(mImsPhone, times(1)).updateImsCallStatus(any(), any());
// no interaction when SRVCC has started, failed, or canceled.
mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_STARTED);
verify(mImsPhone, times(1)).updateImsCallStatus(any(), any());
mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_FAILED);
verify(mImsPhone, times(1)).updateImsCallStatus(any(), any());
mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_CANCELED);
verify(mImsPhone, times(1)).updateImsCallStatus(any(), any());
// interaction when SRVCC has completed
mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED);
verify(mImsPhone, times(2)).updateImsCallStatus(any(), any());
}
@Test
public void testClearAllOrphanedConnectionInfo() throws Exception {
verify(mImsPhone, times(0)).updateImsCallStatus(any(), any());
mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
verify(mImsPhone, times(1)).updateImsCallStatus(any(), any());
}
/** Verifies that the request from ImsService is passed to ImsPhone as expected. */
@Test
@SmallTest
public void testStartAndStopImsTrafficSession() {
IImsTrafficSessionCallback binder = Mockito.mock(IImsTrafficSessionCallback.class);
mMmTelListener.onStartImsTrafficSession(1, MmTelFeature.IMS_TRAFFIC_TYPE_EMERGENCY,
AccessNetworkConstants.AccessNetworkType.EUTRAN,
IMS_TRAFFIC_DIRECTION_OUTGOING, binder);
verify(mImsPhone, times(1)).startImsTraffic(eq(1),
eq(MmTelFeature.IMS_TRAFFIC_TYPE_EMERGENCY),
eq(AccessNetworkConstants.AccessNetworkType.EUTRAN),
eq(IMS_TRAFFIC_DIRECTION_OUTGOING), any());
mMmTelListener.onStopImsTrafficSession(1);
verify(mImsPhone, times(1)).stopImsTraffic(eq(1), any());
mMmTelListener.onStartImsTrafficSession(2, MmTelFeature.IMS_TRAFFIC_TYPE_EMERGENCY_SMS,
AccessNetworkConstants.AccessNetworkType.IWLAN,
IMS_TRAFFIC_DIRECTION_OUTGOING, binder);
verify(mImsPhone, times(1)).startImsTraffic(eq(2),
eq(MmTelFeature.IMS_TRAFFIC_TYPE_EMERGENCY_SMS),
eq(AccessNetworkConstants.AccessNetworkType.IWLAN),
eq(IMS_TRAFFIC_DIRECTION_OUTGOING), any());
mMmTelListener.onStartImsTrafficSession(3, MmTelFeature.IMS_TRAFFIC_TYPE_VOICE,
AccessNetworkConstants.AccessNetworkType.EUTRAN,
IMS_TRAFFIC_DIRECTION_INCOMING, binder);
verify(mImsPhone, times(1)).startImsTraffic(eq(3),
eq(MmTelFeature.IMS_TRAFFIC_TYPE_VOICE),
eq(AccessNetworkConstants.AccessNetworkType.EUTRAN),
eq(IMS_TRAFFIC_DIRECTION_INCOMING), any());
mMmTelListener.onStartImsTrafficSession(4, MmTelFeature.IMS_TRAFFIC_TYPE_VIDEO,
AccessNetworkConstants.AccessNetworkType.EUTRAN,
IMS_TRAFFIC_DIRECTION_OUTGOING, binder);
verify(mImsPhone, times(1)).startImsTraffic(eq(4),
eq(MmTelFeature.IMS_TRAFFIC_TYPE_VIDEO),
eq(AccessNetworkConstants.AccessNetworkType.EUTRAN),
eq(IMS_TRAFFIC_DIRECTION_OUTGOING), any());
mMmTelListener.onStartImsTrafficSession(5, MmTelFeature.IMS_TRAFFIC_TYPE_SMS,
AccessNetworkConstants.AccessNetworkType.EUTRAN,
IMS_TRAFFIC_DIRECTION_OUTGOING, binder);
verify(mImsPhone, times(1)).startImsTraffic(eq(5),
eq(MmTelFeature.IMS_TRAFFIC_TYPE_SMS),
eq(AccessNetworkConstants.AccessNetworkType.EUTRAN),
eq(IMS_TRAFFIC_DIRECTION_OUTGOING), any());
mMmTelListener.onStartImsTrafficSession(6, MmTelFeature.IMS_TRAFFIC_TYPE_REGISTRATION,
AccessNetworkConstants.AccessNetworkType.EUTRAN,
IMS_TRAFFIC_DIRECTION_OUTGOING, binder);
verify(mImsPhone, times(1)).startImsTraffic(eq(6),
eq(MmTelFeature.IMS_TRAFFIC_TYPE_REGISTRATION),
eq(AccessNetworkConstants.AccessNetworkType.EUTRAN),
eq(IMS_TRAFFIC_DIRECTION_OUTGOING), any());
mMmTelListener.onModifyImsTrafficSession(6,
AccessNetworkConstants.AccessNetworkType.IWLAN);
verify(mImsPhone, times(1)).startImsTraffic(eq(6),
eq(MmTelFeature.IMS_TRAFFIC_TYPE_REGISTRATION),
eq(AccessNetworkConstants.AccessNetworkType.IWLAN),
eq(IMS_TRAFFIC_DIRECTION_OUTGOING), any());
}
private void sendCarrierConfigChanged() {
Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
intent.putExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, mPhone.getPhoneId());
mBroadcastReceiver.onReceive(mContext, intent);
processAllMessages();
}
private void assertVtDataUsageUpdated(int expectedToken, long rxBytes, long txBytes)
throws RemoteException {
final ArgumentCaptor<NetworkStats> ifaceStatsCaptor = ArgumentCaptor.forClass(
NetworkStats.class);
final ArgumentCaptor<NetworkStats> uidStatsCaptor = ArgumentCaptor.forClass(
NetworkStats.class);
verify(mVtDataUsageProviderCb).notifyStatsUpdated(eq(expectedToken),
ifaceStatsCaptor.capture(), uidStatsCaptor.capture());
// Default dialer's package uid is not set during test, thus the uid stats looks the same
// as iface stats and the records are always merged into the same entry.
// TODO: Mock different dialer's uid and verify uid stats has corresponding uid in the
// records.
NetworkStats expectedStats = new NetworkStats(0L, 0);
if (rxBytes != 0 || txBytes != 0) {
expectedStats = expectedStats.addEntry(
new Entry(mCTUT.getVtInterface(), UID_ALL, SET_FOREGROUND,
TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, rxBytes, 0L,
txBytes, 0L, 0L));
}
assertNetworkStatsEquals(expectedStats, ifaceStatsCaptor.getValue());
assertNetworkStatsEquals(expectedStats, uidStatsCaptor.getValue());
}
private ImsPhoneConnection placeCallAndMakeActive() {
ImsPhoneConnection connection = placeCall();
ImsCall imsCall = connection.getImsCall();
imsCall.getImsCallSessionListenerProxy().callSessionProgressing(imsCall.getSession(),
new ImsStreamMediaProfile());
imsCall.getImsCallSessionListenerProxy().callSessionStarted(imsCall.getSession(),
new ImsCallProfile());
return connection;
}
private ImsPhoneConnection placeCall() {
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");
}
return connection;
}
private ImsPhoneConnection getImsPhoneConnection(Call.State state,
String number, boolean isIncoming) {
ImsPhoneCall call = mock(ImsPhoneCall.class);
doReturn(state).when(call).getState();
ImsPhoneConnection c = mock(ImsPhoneConnection.class);
doReturn(state).when(c).getState();
doReturn(isIncoming).when(c).isIncoming();
doReturn(call).when(c).getCall();
doReturn(number).when(c).getAddress();
return c;
}
private ImsCallProfile getImsCallProfileForSrvccSync(String callId,
ImsPhoneConnection c, boolean localTone) {
return getImsCallProfileForSrvccSync(callId, c, localTone, null);
}
private ImsCallProfile getImsCallProfileForSrvccSync(String callId,
ImsPhoneConnection c, boolean localTone, List<ConferenceParticipant> participants) {
ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(0,
localTone ? DIRECTION_INACTIVE : DIRECTION_SEND_RECEIVE, 0, 0, 0);
ImsCallProfile profile = new ImsCallProfile(0, 0, null, mediaProfile);
if (c != null) {
ImsCallSession session = mock(ImsCallSession.class);
doReturn(callId).when(session).getCallId();
ImsCall imsCall = mock(ImsCall.class);
doReturn(profile).when(imsCall).getCallProfile();
doReturn(session).when(imsCall).getCallSession();
doReturn(participants).when(imsCall).getConferenceParticipants();
doReturn(imsCall).when(c).getImsCall();
}
return profile;
}
}