blob: 9950b7a0b78b11b10dfdccc1cddf4aff485be715 [file] [log] [blame]
/*
* Copyright (C) 2022 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.services.telephony.domainselection;
import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
import static android.telephony.DomainSelectionService.SELECTOR_TYPE_UT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import android.annotation.NonNull;
import android.content.Context;
import android.os.CancellationSignal;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.PersistableBundle;
import android.telephony.AccessNetworkConstants;
import android.telephony.CarrierConfigManager;
import android.telephony.DisconnectCause;
import android.telephony.DomainSelectionService;
import android.telephony.DomainSelector;
import android.telephony.EmergencyRegResult;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TransportSelectorCallback;
import android.telephony.WwanSelectorCallback;
import android.telephony.ims.ImsManager;
import android.telephony.ims.ImsMmTelManager;
import android.telephony.ims.ImsReasonInfo;
import android.util.Log;
import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
/**
* Unit tests for DomainSelectorBase.
*/
@RunWith(AndroidJUnit4.class)
public class NormalCallDomainSelectorTest {
private static final String TAG = "NormalCallDomainSelectorTest";
private static final int SLOT_ID = 0;
private static final int SUB_ID_1 = 1;
private static final int SUB_ID_2 = 2;
private static final String TEST_CALLID = "01234";
private HandlerThread mHandlerThread;
private NormalCallDomainSelector mNormalCallDomainSelector;
@Mock private Context mMockContext;
@Mock private CarrierConfigManager mMockCarrierConfigMgr;
@Mock private ImsManager mMockImsManager;
@Mock private ImsMmTelManager mMockMmTelManager;
@Mock private ImsStateTracker mMockImsStateTracker;
@Mock private DomainSelectorBase.DestroyListener mMockDestroyListener;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
doReturn(Context.TELEPHONY_IMS_SERVICE).when(mMockContext)
.getSystemServiceName(ImsManager.class);
doReturn(mMockImsManager).when(mMockContext)
.getSystemService(Context.TELEPHONY_IMS_SERVICE);
doReturn(Context.CARRIER_CONFIG_SERVICE).when(mMockContext)
.getSystemServiceName(CarrierConfigManager.class);
doReturn(mMockCarrierConfigMgr).when(mMockContext)
.getSystemService(Context.CARRIER_CONFIG_SERVICE);
doReturn(mMockMmTelManager).when(mMockImsManager).getImsMmTelManager(SUB_ID_1);
doReturn(mMockMmTelManager).when(mMockImsManager).getImsMmTelManager(SUB_ID_2);
doNothing().when(mMockImsStateTracker).removeServiceStateListener(any());
doNothing().when(mMockImsStateTracker).removeImsStateListener(any());
doReturn(true).when(mMockImsStateTracker).isMmTelFeatureAvailable();
// Set up the looper if it does not exist on the test thread.
if (Looper.myLooper() == null) {
Looper.prepare();
}
mHandlerThread = new HandlerThread(
NormalCallDomainSelectorTest.class.getSimpleName());
mHandlerThread.start();
mNormalCallDomainSelector = new NormalCallDomainSelector(mMockContext, SLOT_ID, SUB_ID_1,
mHandlerThread.getLooper(), mMockImsStateTracker, mMockDestroyListener);
}
@After
public void tearDown() throws Exception {
if (mHandlerThread != null) {
mHandlerThread.quit();
}
}
private void initialize(ServiceState serviceState, boolean isImsRegistered,
boolean isImsRegisteredOverWlan, boolean isImsVoiceCapable,
boolean isImsVideoCapable) {
if (serviceState != null) mNormalCallDomainSelector.onServiceStateUpdated(serviceState);
doReturn(isImsRegistered).when(mMockImsStateTracker).isImsStateReady();
doReturn(isImsRegistered).when(mMockImsStateTracker).isImsRegistered();
doReturn(isImsVoiceCapable).when(mMockImsStateTracker).isImsVoiceCapable();
doReturn(isImsVideoCapable).when(mMockImsStateTracker).isImsVideoCapable();
doReturn(isImsRegisteredOverWlan).when(mMockImsStateTracker).isImsRegisteredOverWlan();
mNormalCallDomainSelector.onImsRegistrationStateChanged();
mNormalCallDomainSelector.onImsMmTelCapabilitiesChanged();
}
@Test
public void testInit() {
assertEquals(SLOT_ID, mNormalCallDomainSelector.getSlotId());
assertEquals(SUB_ID_1, mNormalCallDomainSelector.getSubId());
}
@Test
public void testSelectDomainInputParams() {
MockTransportSelectorCallback transportSelectorCallback =
new MockTransportSelectorCallback();
DomainSelectionService.SelectionAttributes attributes =
new DomainSelectionService.SelectionAttributes.Builder(
SLOT_ID, SUB_ID_1, SELECTOR_TYPE_CALLING)
.setCallId(TEST_CALLID)
.setEmergency(false)
.setVideoCall(true)
.setExitedFromAirplaneMode(false)
.build();
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
// Case 1: null inputs
try {
mNormalCallDomainSelector.selectDomain(null, null);
} catch (Exception e) {
fail("Invalid input params not handled." + e.getMessage());
}
// Case 2: null TransportSelectorCallback
try {
mNormalCallDomainSelector.selectDomain(attributes, null);
} catch (Exception e) {
fail("Invalid params (SelectionAttributes) not handled." + e.getMessage());
}
// Case 3: null SelectionAttributes
transportSelectorCallback.mSelectionTerminated = false;
try {
mNormalCallDomainSelector.selectDomain(null, transportSelectorCallback);
} catch (Exception e) {
fail("Invalid params (SelectionAttributes) not handled." + e.getMessage());
}
assertTrue(transportSelectorCallback
.verifyOnSelectionTerminated(DisconnectCause.OUTGOING_FAILURE));
// Case 4: Invalid Subscription-id
attributes = new DomainSelectionService.SelectionAttributes.Builder(
SLOT_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID, SELECTOR_TYPE_CALLING)
.setCallId(TEST_CALLID)
.setEmergency(false)
.setVideoCall(true)
.setExitedFromAirplaneMode(false)
.build();
try {
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
} catch (Exception e) {
fail("Invalid params (SelectionAttributes) not handled." + e.getMessage());
}
assertTrue(transportSelectorCallback
.verifyOnSelectionTerminated(DisconnectCause.OUTGOING_FAILURE));
// Case 5: Invalid SELECTOR_TYPE
attributes =
new DomainSelectionService.SelectionAttributes.Builder(
SLOT_ID, SUB_ID_1, SELECTOR_TYPE_UT)
.setCallId(TEST_CALLID)
.setEmergency(false)
.setVideoCall(true)
.setExitedFromAirplaneMode(false)
.build();
try {
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
} catch (Exception e) {
fail("Invalid params (SelectionAttributes) not handled." + e.getMessage());
}
assertTrue(transportSelectorCallback
.verifyOnSelectionTerminated(DisconnectCause.OUTGOING_FAILURE));
// Case 6: Emergency Call
attributes = new DomainSelectionService.SelectionAttributes.Builder(
SLOT_ID, SUB_ID_1, SELECTOR_TYPE_CALLING)
.setCallId(TEST_CALLID)
.setEmergency(true)
.setVideoCall(true)
.setExitedFromAirplaneMode(false)
.build();
try {
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
} catch (Exception e) {
fail("Invalid params (SelectionAttributes) not handled." + e.getMessage());
}
assertTrue(transportSelectorCallback
.verifyOnSelectionTerminated(DisconnectCause.OUTGOING_FAILURE));
}
@Test
public void testOutOfService() {
MockTransportSelectorCallback transportSelectorCallback =
new MockTransportSelectorCallback();
DomainSelectionService.SelectionAttributes attributes =
new DomainSelectionService.SelectionAttributes.Builder(
SLOT_ID, SUB_ID_1, SELECTOR_TYPE_CALLING)
.setCallId(TEST_CALLID)
.setEmergency(false)
.setVideoCall(true)
.setExitedFromAirplaneMode(false)
.build();
ServiceState serviceState = new ServiceState();
serviceState.setStateOutOfService();
initialize(serviceState, false, false, false, false);
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
assertTrue(transportSelectorCallback
.verifyOnSelectionTerminated(DisconnectCause.OUT_OF_SERVICE));
}
@Test
public void testDomainSelection() {
MockTransportSelectorCallback transportSelectorCallback =
new MockTransportSelectorCallback();
DomainSelectionService.SelectionAttributes attributes =
new DomainSelectionService.SelectionAttributes.Builder(
SLOT_ID, SUB_ID_1, SELECTOR_TYPE_CALLING)
.setCallId(TEST_CALLID)
.setEmergency(false)
.setVideoCall(false)
.setExitedFromAirplaneMode(false)
.build();
// Case 1: WLAN
ServiceState serviceState = new ServiceState();
serviceState.setState(ServiceState.STATE_IN_SERVICE);
initialize(serviceState, true, true, true, true);
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
assertTrue(transportSelectorCallback.verifyOnWlanSelected());
// Case 2: 5G
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
initialize(serviceState, true, false, true, true);
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
assertTrue(transportSelectorCallback.verifyOnWwanSelected());
assertTrue(transportSelectorCallback
.verifyOnDomainSelected(NetworkRegistrationInfo.DOMAIN_PS));
// Case 3: PS -> CS redial
ImsReasonInfo imsReasonInfo = new ImsReasonInfo();
imsReasonInfo.mCode = ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED;
attributes = new DomainSelectionService.SelectionAttributes.Builder(
SLOT_ID, SUB_ID_1, SELECTOR_TYPE_CALLING)
.setCallId(TEST_CALLID)
.setEmergency(false)
.setVideoCall(false)
.setExitedFromAirplaneMode(false)
.setPsDisconnectCause(imsReasonInfo)
.build();
mNormalCallDomainSelector.reselectDomain(attributes);
assertTrue(transportSelectorCallback
.verifyOnDomainSelected(NetworkRegistrationInfo.DOMAIN_CS));
// Case 4: CS call
NetworkRegistrationInfo nwRegistrationInfo = new NetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
NetworkRegistrationInfo.REGISTRATION_STATE_HOME,
AccessNetworkConstants.AccessNetworkType.UTRAN, 0, false,
null, null, null, false, 0, 0, 0);
serviceState.addNetworkRegistrationInfo(nwRegistrationInfo);
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
initialize(serviceState, false, false, false, false);
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
assertTrue(transportSelectorCallback.verifyOnWwanSelected());
assertTrue(transportSelectorCallback
.verifyOnDomainSelected(NetworkRegistrationInfo.DOMAIN_CS));
//Case 5: Backup calling
serviceState.setStateOutOfService();
initialize(serviceState, true, true, true, true);
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
assertTrue(transportSelectorCallback.verifyOnWlanSelected());
}
@Test
public void testWPSCallDomainSelection() {
MockTransportSelectorCallback transportSelectorCallback =
new MockTransportSelectorCallback();
DomainSelectionService.SelectionAttributes attributes =
new DomainSelectionService.SelectionAttributes.Builder(
SLOT_ID, SUB_ID_1, SELECTOR_TYPE_CALLING)
.setNumber("*272121")
.setCallId(TEST_CALLID)
.setEmergency(false)
.setVideoCall(false)
.setExitedFromAirplaneMode(false)
.build();
//Case 1: WPS not supported by IMS
PersistableBundle config = new PersistableBundle();
config.putBoolean(CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL, false);
doReturn(config).when(mMockCarrierConfigMgr).getConfigForSubId(SUB_ID_1);
ServiceState serviceState = new ServiceState();
serviceState.setState(ServiceState.STATE_IN_SERVICE);
initialize(serviceState, true, true, true, true);
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
assertTrue(transportSelectorCallback.verifyOnWwanSelected());
assertTrue(transportSelectorCallback
.verifyOnDomainSelected(NetworkRegistrationInfo.DOMAIN_CS));
//Case 2: WPS supported by IMS and WLAN registered
config.putBoolean(CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL, true);
serviceState.setState(ServiceState.STATE_IN_SERVICE);
initialize(serviceState, true, true, true, true);
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
assertTrue(transportSelectorCallback.verifyOnWlanSelected());
//Case 2: WPS supported by IMS and LTE registered
config.putBoolean(CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL, true);
serviceState.setState(ServiceState.STATE_IN_SERVICE);
initialize(serviceState, true, false, true, true);
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
assertTrue(transportSelectorCallback.verifyOnWwanSelected());
assertTrue(transportSelectorCallback
.verifyOnDomainSelected(NetworkRegistrationInfo.DOMAIN_PS));
}
static class MockTransportSelectorCallback implements TransportSelectorCallback,
WwanSelectorCallback {
public boolean mCreated;
public boolean mWlanSelected;
public boolean mWwanSelected;
public boolean mSelectionTerminated;
public boolean mDomainSelected;
int mCauseCode;
int mSelectedDomain;
@Override
public synchronized void onCreated(DomainSelector selector) {
Log.d(TAG, "onCreated");
mCreated = true;
notifyAll();
}
public boolean verifyOnCreated() {
mCreated = false;
Log.d(TAG, "verifyOnCreated");
waitForCallback(mCreated);
return mCreated;
}
@Override
public synchronized void onWlanSelected() {
Log.d(TAG, "onWlanSelected");
mWlanSelected = true;
notifyAll();
}
public boolean verifyOnWlanSelected() {
Log.d(TAG, "verifyOnWlanSelected");
waitForCallback(mWlanSelected);
return mWlanSelected;
}
@Override
public synchronized WwanSelectorCallback onWwanSelected() {
mWwanSelected = true;
notifyAll();
return (WwanSelectorCallback) this;
}
@Override
public void onWwanSelected(final Consumer<WwanSelectorCallback> consumer) {
mWwanSelected = true;
Executors.newSingleThreadExecutor().execute(() -> {
consumer.accept(this);
});
}
public boolean verifyOnWwanSelected() {
waitForCallback(mWwanSelected);
return mWwanSelected;
}
@Override
public synchronized void onSelectionTerminated(int cause) {
Log.i(TAG, "onSelectionTerminated - called");
mCauseCode = cause;
mSelectionTerminated = true;
notifyAll();
}
public boolean verifyOnSelectionTerminated(int cause) {
Log.i(TAG, "verifyOnSelectionTerminated - called");
waitForCallback(mSelectionTerminated);
return (mSelectionTerminated && cause == mCauseCode);
}
private synchronized void waitForCallback(boolean condition) {
long now = System.currentTimeMillis();
long deadline = now + 1000;
try {
while (!condition && now < deadline) {
wait(deadline - now);
now = System.currentTimeMillis();
}
} catch (Exception e) {
Log.i(TAG, e.getMessage());
}
}
@Override
public void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
int scanType,
@NonNull CancellationSignal signal,
@NonNull Consumer<EmergencyRegResult> consumer) {
Log.i(TAG, "onRequestEmergencyNetworkScan - called");
}
public synchronized void onDomainSelected(@NetworkRegistrationInfo.Domain int domain) {
Log.i(TAG, "onDomainSelected - called");
mSelectedDomain = domain;
mDomainSelected = true;
notifyAll();
}
public boolean verifyOnDomainSelected(int domain) {
Log.i(TAG, "verifyOnDomainSelected - called");
mDomainSelected = false;
waitForCallback(mDomainSelected);
return (domain == mSelectedDomain);
}
}
}