Add domain selector for normal calls
Bug: 246719281
Test: atest TeleServiceTests:NormalCallDomainSelectorTest
Change-Id: Iaf307021b6259c49d36e0a68f97ccc1f5c1fb759
diff --git a/src/com/android/services/telephony/domainselection/NormalCallDomainSelector.java b/src/com/android/services/telephony/domainselection/NormalCallDomainSelector.java
new file mode 100644
index 0000000..82057d3
--- /dev/null
+++ b/src/com/android/services/telephony/domainselection/NormalCallDomainSelector.java
@@ -0,0 +1,299 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.content.Context;
+import android.os.Looper;
+import android.telephony.Annotation.DisconnectCauses;
+import android.telephony.DisconnectCause;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TransportSelectorCallback;
+import android.telephony.ims.ImsReasonInfo;
+
+/**
+ * Implements domain selector for outgoing non-emergency calls.
+ */
+public class NormalCallDomainSelector extends DomainSelectorBase implements
+ ImsStateTracker.ImsStateListener, ImsStateTracker.ServiceStateListener {
+
+ private static final String LOG_TAG = "NCDS";
+
+ private boolean mStopDomainSelection = true;
+ private ServiceState mServiceState;
+ private boolean mImsRegStateReceived;
+ private boolean mMmTelCapabilitiesReceived;
+ private boolean mReselectDomain;
+
+ public NormalCallDomainSelector(Context context, int slotId, int subId, @NonNull Looper looper,
+ @NonNull ImsStateTracker imsStateTracker,
+ @NonNull DestroyListener destroyListener) {
+ super(context, slotId, subId, looper, imsStateTracker, destroyListener, LOG_TAG);
+
+ if (SubscriptionManager.isValidSubscriptionId(subId)) {
+ logd("Subscribing to state callbacks. Subid:" + subId);
+ mImsStateTracker.addServiceStateListener(this);
+ mImsStateTracker.addImsStateListener(this);
+ } else {
+ loge("Invalid Subscription. Subid:" + subId);
+ }
+ }
+
+ @Override
+ public void selectDomain(SelectionAttributes attributes, TransportSelectorCallback callback) {
+ mSelectionAttributes = attributes;
+ mTransportSelectorCallback = callback;
+ mStopDomainSelection = false;
+
+ if (callback == null) {
+ loge("Invalid params: TransportSelectorCallback is null");
+ return;
+ }
+
+ if (attributes == null) {
+ loge("Invalid params: SelectionAttributes are null");
+ notifySelectionTerminated(DisconnectCause.OUTGOING_FAILURE);
+ return;
+ }
+
+ int subId = attributes.getSubId();
+ boolean validSubscriptionId = SubscriptionManager.isValidSubscriptionId(subId);
+ if (attributes.getSelectorType() != SELECTOR_TYPE_CALLING || attributes.isEmergency()
+ || !validSubscriptionId) {
+ loge("Domain Selection stopped. SelectorType:" + attributes.getSelectorType()
+ + ", isEmergency:" + attributes.isEmergency()
+ + ", ValidSubscriptionId:" + validSubscriptionId);
+
+ notifySelectionTerminated(DisconnectCause.OUTGOING_FAILURE);
+ return;
+ }
+
+ if (subId == getSubId()) {
+ logd("NormalCallDomainSelection triggered. Sub-id:" + subId);
+ post(() -> selectDomain());
+ } else {
+ loge("Subscription-ids doesn't match. This instance is associated with sub-id:"
+ + getSubId() + ", requested sub-id:" + subId);
+ // TODO: Throw anamoly here. This condition should never occur.
+ }
+ }
+
+ @Override
+ public void reselectDomain(SelectionAttributes attributes) {
+ logd("reselectDomain called");
+ mReselectDomain = true;
+ selectDomain(attributes, mTransportSelectorCallback);
+ }
+
+ @Override
+ public synchronized void finishSelection() {
+ logd("finishSelection");
+ mStopDomainSelection = true;
+ mImsStateTracker.removeServiceStateListener(this);
+ mImsStateTracker.removeImsStateListener(this);
+ mSelectionAttributes = null;
+ mTransportSelectorCallback = null;
+ }
+
+ /**
+ * Cancel an ongoing selection operation. It is up to the DomainSelectionService
+ * to clean up all ongoing operations with the framework.
+ */
+ @Override
+ public void cancelSelection() {
+ logd("cancelSelection");
+ mStopDomainSelection = true;
+ mReselectDomain = false;
+ if (mTransportSelectorCallback != null) {
+ mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_CANCELED);
+ }
+ finishSelection();
+ }
+
+ @Override
+ public void onImsRegistrationStateChanged() {
+ logd("onImsRegistrationStateChanged");
+ mImsRegStateReceived = true;
+ selectDomain();
+ }
+
+ @Override
+ public void onImsMmTelCapabilitiesChanged() {
+ logd("onImsMmTelCapabilitiesChanged");
+ mMmTelCapabilitiesReceived = true;
+ selectDomain();
+ }
+
+ @Override
+ public void onImsMmTelFeatureAvailableChanged() {
+ logd("onImsMmTelFeatureAvailableChanged");
+ selectDomain();
+ }
+
+ @Override
+ public void onServiceStateUpdated(ServiceState serviceState) {
+ logd("onServiceStateUpdated");
+ mServiceState = serviceState;
+ selectDomain();
+ }
+
+ private void notifyPsSelected() {
+ logd("notifyPsSelected");
+ mStopDomainSelection = true;
+ if (mImsStateTracker.isImsRegisteredOverWlan()) {
+ logd("WLAN selected");
+ mTransportSelectorCallback.onWlanSelected();
+ } else {
+ if (mWwanSelectorCallback == null) {
+ mTransportSelectorCallback.onWwanSelected((callback) -> {
+ mWwanSelectorCallback = callback;
+ notifyPsSelectedInternal();
+ });
+ } else {
+ notifyPsSelectedInternal();
+ }
+ }
+ }
+
+ private void notifyPsSelectedInternal() {
+ if (mWwanSelectorCallback != null) {
+ logd("notifyPsSelected - onWwanSelected");
+ mWwanSelectorCallback.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS);
+ } else {
+ loge("wwanSelectorCallback is null");
+ mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_FAILURE);
+ }
+ }
+
+ private void notifyCsSelected() {
+ logd("notifyCsSelected");
+ mStopDomainSelection = true;
+ if (mWwanSelectorCallback == null) {
+ mTransportSelectorCallback.onWwanSelected((callback) -> {
+ mWwanSelectorCallback = callback;
+ notifyCsSelectedInternal();
+ });
+ } else {
+ notifyCsSelectedInternal();
+ }
+ }
+
+ private void notifyCsSelectedInternal() {
+ if (mWwanSelectorCallback != null) {
+ logd("wwanSelectorCallback -> onDomainSelected(DOMAIN_CS)");
+ mWwanSelectorCallback.onDomainSelected(NetworkRegistrationInfo.DOMAIN_CS);
+ } else {
+ loge("wwanSelectorCallback is null");
+ mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_FAILURE);
+ }
+ }
+
+ private void notifySelectionTerminated(@DisconnectCauses int cause) {
+ mStopDomainSelection = true;
+ if (mTransportSelectorCallback != null) {
+ mTransportSelectorCallback.onSelectionTerminated(cause);
+ finishSelection();
+ }
+ }
+
+ private synchronized void selectDomain() {
+ if (mStopDomainSelection || mSelectionAttributes == null
+ || mTransportSelectorCallback == null) {
+ logd("Domain Selection is stopped.");
+ return;
+ }
+
+ if (mServiceState == null) {
+ logd("Waiting for ServiceState callback.");
+ return;
+ } else if (mServiceState.getState() == ServiceState.STATE_OUT_OF_SERVICE
+ || mServiceState.getState() == ServiceState.STATE_POWER_OFF
+ || mServiceState.getState() == ServiceState.STATE_EMERGENCY_ONLY) {
+ loge("Cannot place call in current ServiceState: " + mServiceState.getState());
+ notifySelectionTerminated(DisconnectCause.OUT_OF_SERVICE);
+ return;
+ }
+
+ // Check if this is a re-dial scenario
+ // IMS -> CS
+ ImsReasonInfo imsReasonInfo = mSelectionAttributes.getPsDisconnectCause();
+ if (mReselectDomain && imsReasonInfo != null) {
+ logd("PsDisconnectCause:" + imsReasonInfo.mCode);
+ mReselectDomain = false;
+ if (imsReasonInfo.mCode == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED) {
+ logd("Redialing over CS");
+ notifyCsSelected();
+ return;
+ } else {
+ logd("Redialing cancelled.");
+ // Not a valid redial
+ notifySelectionTerminated(DisconnectCause.NOT_VALID);
+ return;
+ }
+ }
+
+ // CS -> IMS
+ // TODO: @PreciseDisconnectCauses doesn't contain cause code related to redial on IMS.
+ if (mReselectDomain /*mSelectionAttributes.getCsDisconnectCause() == IMS_REDIAL_CODE*/) {
+ logd("Redialing cancelled.");
+ // Not a valid redial
+ notifySelectionTerminated(DisconnectCause.NOT_VALID);
+ return;
+ }
+
+ if (mImsStateTracker.isMmTelFeatureAvailable()) {
+
+ if (!mImsRegStateReceived || !mMmTelCapabilitiesReceived) {
+ loge("Waiting for ImsState and MmTelCapabilities callbacks");
+ return;
+ }
+
+ if (!mImsStateTracker.isImsRegistered()) {
+ logd("IMS is NOT registered");
+ notifyCsSelected();
+ return;
+ }
+
+ if (mSelectionAttributes.isVideoCall()) {
+ logd("It's a video call");
+ if (mImsStateTracker.isImsVideoCapable()) {
+ logd("IMS is video capable");
+ notifyPsSelected();
+ } else {
+ logd("IMS is not video capable. Ending the call");
+ notifySelectionTerminated(DisconnectCause.OUTGOING_FAILURE);
+ }
+ } else if (mImsStateTracker.isImsVoiceCapable()) {
+ logd("IMS is voice capable");
+ // Voice call over PS
+ notifyPsSelected();
+ } else {
+ logd("IMS is not voice capable");
+ // Voice call CS fallback
+ notifyCsSelected();
+ }
+ } else {
+ logd("IMS is not registered or unavailable");
+ notifyCsSelected();
+ }
+ }
+}
diff --git a/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java b/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
index 6f47ee6..13db06b 100644
--- a/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
+++ b/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
@@ -91,9 +91,8 @@
selector = new EmergencyCallDomainSelector(context, slotId, subId, looper,
imsStateTracker, listener);
} else {
- // TODO(ag/20024470) uncomment when normal call domain selector is ready.
- /*selector = new NormalCallDomainSelector(context, slotId, subId, looper,
- imsStateTracker, listener);*/
+ selector = new NormalCallDomainSelector(context, slotId, subId, looper,
+ imsStateTracker, listener);
}
break;
case SELECTOR_TYPE_SMS:
diff --git a/tests/src/com/android/services/telephony/domainselection/NormalCallDomainSelectorTest.java b/tests/src/com/android/services/telephony/domainselection/NormalCallDomainSelectorTest.java
new file mode 100644
index 0000000..832e480
--- /dev/null
+++ b/tests/src/com/android/services/telephony/domainselection/NormalCallDomainSelectorTest.java
@@ -0,0 +1,408 @@
+/*
+ * 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.telephony.AccessNetworkConstants;
+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 ImsManager mMockImsManager;
+ @Mock private ImsMmTelManager mMockMmTelManager;
+ @Mock private ServiceState mMockServiceState;
+ @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(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.");
+ }
+
+ // Case 2: null TransportSelectorCallback
+ try {
+ mNormalCallDomainSelector.selectDomain(attributes, null);
+ } catch (Exception e) {
+ fail("Invalid params (SelectionAttributes) not handled.");
+ }
+
+ // Case 3: null SelectionAttributes
+ transportSelectorCallback.mSelectionTerminated = false;
+ try {
+ mNormalCallDomainSelector.selectDomain(null, transportSelectorCallback);
+ } catch (Exception e) {
+ fail("Invalid params (SelectionAttributes) not handled.");
+ }
+
+ 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.");
+ }
+
+ 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(null, transportSelectorCallback);
+ } catch (Exception e) {
+ fail("Invalid params (SelectionAttributes) not handled.");
+ }
+
+ assertTrue(transportSelectorCallback
+ .verifyOnSelectionTerminated(DisconnectCause.OUTGOING_FAILURE));
+
+ // Case 6: Emergency Call
+ attributes = new DomainSelectionService.SelectionAttributes.Builder(
+ SLOT_ID, SUB_ID_1, SELECTOR_TYPE_UT)
+ .setCallId(TEST_CALLID)
+ .setEmergency(true)
+ .setVideoCall(true)
+ .setExitedFromAirplaneMode(false)
+ .build();
+ try {
+ mNormalCallDomainSelector.selectDomain(null, transportSelectorCallback);
+ } catch (Exception e) {
+ fail("Invalid params (SelectionAttributes) not handled.");
+ }
+
+ 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();
+ mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+ ServiceState serviceState = new ServiceState();
+ serviceState.setStateOutOfService();
+ mNormalCallDomainSelector.onServiceStateUpdated(serviceState);
+ 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));
+ }
+
+ class MockTransportSelectorCallback implements TransportSelectorCallback, WwanSelectorCallback {
+ public boolean mCreated = false;
+ public boolean mWlanSelected = false;
+ public boolean mWwanSelected = false;
+ public boolean mSelectionTerminated = false;
+ int mCauseCode = 0;
+ int mSelectedDomain = 0;
+
+ @Override
+ public synchronized void onCreated(DomainSelector selector) {
+ Log.d(TAG, "onCreated");
+ mCreated = true;
+ notifyAll();
+ }
+
+ public boolean verifyOnCreated() {
+ mCreated = false;
+ Log.d(TAG, "verifyOnCreated");
+ waitForCallback();
+ return mCreated;
+ }
+
+ @Override
+ public synchronized void onWlanSelected() {
+ Log.d(TAG, "onWlanSelected");
+ mWlanSelected = true;
+ notifyAll();
+ }
+
+ public boolean verifyOnWlanSelected() {
+ Log.d(TAG, "verifyOnWlanSelected");
+ waitForCallback();
+ 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();
+ 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");
+ if (!mSelectionTerminated) {
+ waitForCallback();
+ }
+ return (mSelectionTerminated && cause == mCauseCode);
+ }
+
+ private synchronized void waitForCallback() {
+ try {
+ wait(1000);
+ } catch (Exception e) {
+ return;
+ }
+ }
+
+ @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;
+ notifyAll();
+ }
+
+ public boolean verifyOnDomainSelected(int domain) {
+ Log.i(TAG, "verifyOnDomainSelected - called");
+ waitForCallback();
+ return (domain == mSelectedDomain);
+ }
+ }
+}