Add domain selector for SMS

This is for normal and emergency SMS domain selectors. Each domain
selector implements the domain selection (PS or CS) to route the MO SMS
to the appropriate domain based on the current device conditions.

Bug: 243377249
Test: atest SmsDomainSelectorTest
Test: atest EmergencySmsDomainSelectorTest
Test: manual (verify IMS/GSM/CDMA MO SMS for normal/emergency SMS when domain selection disabled)
Test: manual (verify retry SMS for normal/emergency SMS when domain selection disabled)
Test: manual (verify IMS/GSM/CDMA MO SMS for normal/emergency SMS when domain selection enabled)
Test: manual (verify retry SMS for normal/emergency SMS when domain selection enabled)
Change-Id: I4367ceab4673017248156162fe691a9d92eda305
diff --git a/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelector.java b/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelector.java
new file mode 100644
index 0000000..cd588e1
--- /dev/null
+++ b/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelector.java
@@ -0,0 +1,291 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.content.Context;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.BarringInfo;
+import android.telephony.CarrierConfigManager;
+import android.telephony.DataSpecificRegistrationInfo;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.telephony.VopsSupportInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Implements an emergency SMS domain selector for sending an emergency SMS.
+ */
+public class EmergencySmsDomainSelector extends SmsDomainSelector implements
+        ImsStateTracker.BarringInfoListener, ImsStateTracker.ServiceStateListener {
+    /**
+     * Stores the configuration value of
+     * {@link CarrierConfigManager#KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL}.
+     * This value is always updated whenever the domain selection is requested.
+     */
+    private Boolean mEmergencySmsOverImsSupportedByConfig;
+    private ServiceState mServiceState;
+    private boolean mServiceStateReceived;
+    private BarringInfo mBarringInfo;
+    private boolean mBarringInfoReceived;
+
+    public EmergencySmsDomainSelector(Context context, int slotId, int subId,
+            @NonNull Looper looper, @NonNull ImsStateTracker imsStateTracker,
+            @NonNull DestroyListener listener) {
+        super(context, slotId, subId, looper, imsStateTracker, listener,
+                "DomainSelector-EmergencySMS");
+
+        mImsStateTracker.addServiceStateListener(this);
+        mImsStateTracker.addBarringInfoListener(this);
+    }
+
+    @Override
+    public void destroy() {
+        if (mDestroyed) {
+            return;
+        }
+        mImsStateTracker.removeServiceStateListener(this);
+        mImsStateTracker.removeBarringInfoListener(this);
+        super.destroy();
+    }
+
+    @Override
+    public void finishSelection() {
+        super.finishSelection();
+        mServiceStateReceived = false;
+        mServiceState = null;
+        mBarringInfoReceived = false;
+        mBarringInfo = null;
+        mEmergencySmsOverImsSupportedByConfig = null;
+    }
+
+    @Override
+    public void onBarringInfoUpdated(BarringInfo barringInfo) {
+        mBarringInfoReceived = true;
+        mBarringInfo = barringInfo;
+        sendMessageForDomainSelection();
+    }
+
+    @Override
+    public void onServiceStateUpdated(ServiceState serviceState) {
+        mServiceStateReceived = true;
+        mServiceState = serviceState;
+        sendMessageForDomainSelection();
+    }
+
+    /**
+     * Checks whether the domain selector is ready to select the domain or not.
+     * The emergency SMS requires to be updated for the {@link ServiceState} and
+     * {@link BarringInfo} to confirm that the cellular network supports to send emergency SMS
+     * messages over IMS.
+     */
+    @VisibleForTesting
+    public boolean isDomainSelectionReady() {
+        return mServiceStateReceived && mBarringInfoReceived;
+    }
+
+    @Override
+    protected boolean isSmsOverImsAvailable() {
+        if (super.isSmsOverImsAvailable()) {
+            /**
+             * Even though IMS is successfully registered, the cellular domain should be
+             * available for the emergency SMS according to the carrier's requirement
+             * when {@link CarrierConfigManager#KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL} is set
+             * to true.
+             */
+            if (isEmergencySmsOverImsSupportedByConfig()) {
+                /**
+                 * Emergency SMS should be supported via emergency PDN.
+                 * If this condition is false, then need to fallback to CS network
+                 * because the current PS network does not allow the emergency service.
+                 */
+                return isNetworkAvailableForImsEmergencySms();
+            }
+
+            // Emergency SMS is supported via IMS PDN.
+            return true;
+        }
+
+        return isImsEmergencySmsAvailable();
+    }
+
+    @Override
+    protected void selectDomain() {
+        if (!isDomainSelectionRequested()) {
+            logi("Domain selection is not requested!");
+            return;
+        }
+
+        if (!isDomainSelectionReady()) {
+            logd("Wait for the readiness of the domain selection!");
+            return;
+        }
+
+        logi("selectDomain: " + mImsStateTracker.imsStateToString());
+
+        if (isSmsOverImsAvailable()) {
+            if (mImsStateTracker.isImsRegisteredOverWlan()) {
+                if (!isEmergencySmsOverImsSupportedByConfig()) {
+                    notifyWlanSelected();
+                    return;
+                }
+
+                /**
+                 * When {@link CarrierConfigManager#KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL}
+                 * is set to true, the emergency SMS supports on the LTE network using the
+                 * emergency PDN. So, considering EUTRAN only at this point.
+                 */
+                logi("DomainSelected: WLAN >> WWAN");
+            }
+            notifyWwanSelected(NetworkRegistrationInfo.DOMAIN_PS);
+        } else {
+            notifyWwanSelected(NetworkRegistrationInfo.DOMAIN_CS);
+        }
+    }
+
+    /**
+     * Checks if the emergency SMS messages over IMS is available according to the carrier
+     * configuration and the current network states.
+     */
+    private boolean isImsEmergencySmsAvailable() {
+        boolean emergencySmsOverImsSupportedByConfig = isEmergencySmsOverImsSupportedByConfig();
+        boolean networkAvailable = isNetworkAvailableForImsEmergencySms();
+
+        logi("isImsEmergencySmsAvailable: "
+                + "emergencySmsOverIms=" + emergencySmsOverImsSupportedByConfig
+                + ", mmTelFeatureAvailable=" + mImsStateTracker.isMmTelFeatureAvailable()
+                + ", networkAvailable=" + networkAvailable);
+
+        return emergencySmsOverImsSupportedByConfig
+                && mImsStateTracker.isMmTelFeatureAvailable()
+                && networkAvailable;
+    }
+
+    /**
+     * Checks if sending emergency SMS messages over IMS is supported when in LTE/limited LTE
+     * (Emergency only) service mode from the carrier configuration.
+     */
+    private boolean isEmergencySmsOverImsSupportedByConfig() {
+        if (mEmergencySmsOverImsSupportedByConfig == null) {
+            CarrierConfigManager ccm = mContext.getSystemService(CarrierConfigManager.class);
+
+            if (ccm == null) {
+                loge("CarrierConfigManager is null");
+                return false;
+            }
+
+            PersistableBundle b = ccm.getConfigForSubId(getSubId());
+
+            if (b == null) {
+                loge("PersistableBundle is null");
+                return false;
+            }
+
+            mEmergencySmsOverImsSupportedByConfig = b.getBoolean(
+                    CarrierConfigManager.KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL);
+        }
+
+        return mEmergencySmsOverImsSupportedByConfig;
+    }
+
+    /**
+     * Checks if the emergency service is available in the LTE service mode.
+     */
+    private boolean isLteEmergencyAvailableInService() {
+        if (mServiceState == null) {
+            return false;
+        }
+
+        final NetworkRegistrationInfo regInfo = mServiceState.getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        if (regInfo != null
+                && regInfo.getAccessNetworkTechnology() == TelephonyManager.NETWORK_TYPE_LTE
+                && regInfo.isRegistered()) {
+            return isEmergencyServiceSupported(regInfo) && isEmergencyServiceAllowed();
+        }
+        return false;
+    }
+
+    /**
+     * Checks if the emergency service is available in the limited LTE service(Emergency only) mode.
+     */
+    private boolean isLteEmergencyAvailableInLimitedService() {
+        if (mServiceState == null) {
+            return false;
+        }
+
+        final NetworkRegistrationInfo regInfo = mServiceState.getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        if (regInfo != null
+                && regInfo.getAccessNetworkTechnology() == TelephonyManager.NETWORK_TYPE_LTE
+                && regInfo.isEmergencyEnabled()) {
+            return isEmergencyServiceSupported(regInfo) && isEmergencyServiceAllowed();
+        }
+        return false;
+    }
+
+    /**
+     * Checks if the network is available for the IMS emergency SMS.
+     */
+    private boolean isNetworkAvailableForImsEmergencySms() {
+        return isLteEmergencyAvailableInService()
+                || isLteEmergencyAvailableInLimitedService();
+    }
+
+    /**
+     * Checks if the emergency service is supported by the network.
+     *
+     * This checks if "Emergency bearer services indicator (EMC-BS)" field (bits) set to
+     * the "Emergency bearer services in S1 mode supported".
+     *
+     * @return {@code true} if the emergency service is supported by the network,
+     *         {@code false} otherwise.
+     */
+    private boolean isEmergencyServiceSupported(@NonNull NetworkRegistrationInfo regInfo) {
+        final DataSpecificRegistrationInfo dsRegInfo = regInfo.getDataSpecificInfo();
+        if (dsRegInfo != null) {
+            final VopsSupportInfo vopsSupportInfo = dsRegInfo.getVopsSupportInfo();
+            return vopsSupportInfo != null
+                    && vopsSupportInfo.isEmergencyServiceSupported();
+        }
+        return false;
+    }
+
+    /**
+     * Checks if the emergency service is allowed (not barred) by the network.
+     *
+     * This checks if SystemInformationBlockType2 includes the ac-BarringInfo and
+     * with the ac-BarringForEmergency set to FALSE or
+     * if the SystemInformationBlockType2 does not include the ac-BarringInfo.
+     *
+     * @return {@code true} if the emergency service is allowed by the network,
+     *         {@code false} otherwise.
+     */
+    private boolean isEmergencyServiceAllowed() {
+        if (mBarringInfo == null) {
+            return true;
+        }
+        final BarringInfo.BarringServiceInfo bsi =
+                mBarringInfo.getBarringServiceInfo(BarringInfo.BARRING_SERVICE_TYPE_EMERGENCY);
+        return !bsi.isBarred();
+    }
+}
diff --git a/src/com/android/services/telephony/domainselection/SmsDomainSelector.java b/src/com/android/services/telephony/domainselection/SmsDomainSelector.java
new file mode 100644
index 0000000..e7c9ac5
--- /dev/null
+++ b/src/com/android/services/telephony/domainselection/SmsDomainSelector.java
@@ -0,0 +1,207 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.content.Context;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.DisconnectCause;
+import android.telephony.DomainSelectionService;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.TransportSelectorCallback;
+
+/**
+ * Implements SMS domain selector for sending MO SMS.
+ */
+public class SmsDomainSelector extends DomainSelectorBase implements
+        ImsStateTracker.ImsStateListener {
+    protected static final int EVENT_SELECT_DOMAIN = 101;
+
+    protected boolean mDestroyed = false;
+    private boolean mDomainSelectionRequested = false;
+
+    public SmsDomainSelector(Context context, int slotId, int subId, @NonNull Looper looper,
+            @NonNull ImsStateTracker imsStateTracker, @NonNull DestroyListener listener) {
+        this(context, slotId, subId, looper, imsStateTracker, listener, "DomainSelector-SMS");
+    }
+
+    protected SmsDomainSelector(Context context, int slotId, int subId, @NonNull Looper looper,
+            @NonNull ImsStateTracker imsStateTracker, @NonNull DestroyListener listener,
+            String logTag) {
+        super(context, slotId, subId, looper, imsStateTracker, listener, logTag);
+    }
+
+    @Override
+    public void destroy() {
+        if (mDestroyed) {
+            return;
+        }
+        logd("destroy");
+        mDestroyed = true;
+        mImsStateTracker.removeImsStateListener(this);
+        super.destroy();
+    }
+
+    @Override
+    public void handleMessage(@NonNull Message msg) {
+        switch (msg.what) {
+            case EVENT_SELECT_DOMAIN:
+                selectDomain();
+                break;
+            default:
+                super.handleMessage(msg);
+                break;
+        }
+    }
+
+    @Override
+    public void cancelSelection() {
+        logi("cancelSelection");
+        finishSelection();
+    }
+
+    @Override
+    public void reselectDomain(@NonNull SelectionAttributes attr) {
+        if (isDomainSelectionRequested()) {
+            // The domain selection is already requested,
+            // so we don't need to request it again before completing the previous task.
+            logi("Domain selection is already running.");
+            return;
+        }
+
+        logi("reselectDomain");
+        mSelectionAttributes = attr;
+        setDomainSelectionRequested(true);
+        obtainMessage(EVENT_SELECT_DOMAIN).sendToTarget();
+    }
+
+    @Override
+    public void finishSelection() {
+        logi("finishSelection");
+        setDomainSelectionRequested(false);
+        mSelectionAttributes = null;
+        mTransportSelectorCallback = null;
+        mWwanSelectorCallback = null;
+        destroy();
+    }
+
+    @Override
+    public void selectDomain(SelectionAttributes attr, TransportSelectorCallback callback) {
+        if (isDomainSelectionRequested()) {
+            // The domain selection is already requested,
+            // so we don't need to request it again before completing the previous task.
+            logi("Domain selection is already running.");
+            return;
+        }
+        mSelectionAttributes = attr;
+        mTransportSelectorCallback = callback;
+        setDomainSelectionRequested(true);
+        mImsStateTracker.addImsStateListener(this);
+        obtainMessage(EVENT_SELECT_DOMAIN).sendToTarget();
+    }
+
+    @Override
+    public void onImsMmTelFeatureAvailableChanged() {
+        sendMessageForDomainSelection();
+    }
+
+    @Override
+    public void onImsRegistrationStateChanged() {
+        sendMessageForDomainSelection();
+    }
+
+    @Override
+    public void onImsMmTelCapabilitiesChanged() {
+        sendMessageForDomainSelection();
+    }
+
+    protected boolean isSmsOverImsAvailable() {
+        return mImsStateTracker.isImsSmsCapable()
+                && mImsStateTracker.isImsRegistered()
+                && mImsStateTracker.isMmTelFeatureAvailable();
+    }
+
+    protected void selectDomain() {
+        if (!isDomainSelectionRequested()) {
+            logi("Domain selection is not requested!");
+            return;
+        }
+
+        logi("selectDomain: " + mImsStateTracker.imsStateToString());
+
+        if (isSmsOverImsAvailable()) {
+            if (mImsStateTracker.isImsRegisteredOverWlan()) {
+                notifyWlanSelected();
+                return;
+            }
+            notifyWwanSelected(NetworkRegistrationInfo.DOMAIN_PS);
+        } else {
+            notifyWwanSelected(NetworkRegistrationInfo.DOMAIN_CS);
+        }
+    }
+
+    protected void sendMessageForDomainSelection() {
+        // If the event is already queued to this handler,
+        // it will be removed first to avoid the duplicate operation.
+        removeMessages(EVENT_SELECT_DOMAIN);
+        // Since the IMS state may have already been posted,
+        // proceed with the domain selection after processing all pending messages.
+        obtainMessage(EVENT_SELECT_DOMAIN).sendToTarget();
+    }
+
+    protected boolean isDomainSelectionRequested() {
+        return mDomainSelectionRequested;
+    }
+
+    protected void setDomainSelectionRequested(boolean requested) {
+        if (mDomainSelectionRequested != requested) {
+            logd("DomainSelectionRequested: " + mDomainSelectionRequested + " >> " + requested);
+            mDomainSelectionRequested = requested;
+        }
+    }
+
+    protected void notifyWlanSelected() {
+        logi("DomainSelected: WLAN");
+        mTransportSelectorCallback.onWlanSelected();
+        setDomainSelectionRequested(false);
+    }
+
+    protected void notifyWwanSelected(@NetworkRegistrationInfo.Domain int domain) {
+        if (mWwanSelectorCallback == null) {
+            mTransportSelectorCallback.onWwanSelected((callback) -> {
+                mWwanSelectorCallback = callback;
+                notifyWwanSelectedInternal(domain);
+            });
+        } else {
+            notifyWwanSelectedInternal(domain);
+        }
+
+        setDomainSelectionRequested(false);
+    }
+
+    protected void notifyWwanSelectedInternal(@NetworkRegistrationInfo.Domain int domain) {
+        logi("DomainSelected: WWAN/" + DomainSelectionService.getDomainName(domain));
+
+        if (mWwanSelectorCallback != null) {
+            mWwanSelectorCallback.onDomainSelected(domain);
+        } else {
+            mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.LOCAL);
+        }
+    }
+}
diff --git a/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java b/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
index d537281..6f47ee6 100644
--- a/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
+++ b/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
@@ -97,14 +97,13 @@
                     }
                     break;
                 case SELECTOR_TYPE_SMS:
-                    // TODO(ag/20075167) uncomment when SMS domain selector is ready.
-                    /*if (isEmergency) {
+                    if (isEmergency) {
                         selector = new EmergencySmsDomainSelector(context, slotId, subId, looper,
                                 imsStateTracker, listener);
                     } else {
                         selector = new SmsDomainSelector(context, slotId, subId, looper,
                                 imsStateTracker, listener);
-                    }*/
+                    }
                     break;
                 default:
                     // Not reachable.
diff --git a/tests/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelectorTest.java b/tests/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelectorTest.java
new file mode 100644
index 0000000..8b63530
--- /dev/null
+++ b/tests/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelectorTest.java
@@ -0,0 +1,799 @@
+/*
+ * 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_SMS;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.BarringInfo;
+import android.telephony.CarrierConfigManager;
+import android.telephony.DataSpecificRegistrationInfo;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.telephony.TransportSelectorCallback;
+import android.telephony.VopsSupportInfo;
+import android.telephony.WwanSelectorCallback;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableLooper;
+import android.util.SparseArray;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.TestContext;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Consumer;
+
+/**
+ * Unit tests for EmergencySmsDomainSelector.
+ */
+@RunWith(AndroidJUnit4.class)
+public class EmergencySmsDomainSelectorTest {
+    private static final int SLOT_0 = 0;
+    private static final int SUB_1 = 1;
+
+    @Mock private ServiceState mServiceState;
+    @Mock private TransportSelectorCallback mTransportSelectorCallback;
+    @Mock private WwanSelectorCallback mWwanSelectorCallback;
+    @Mock private VopsSupportInfo mVopsSupportInfo;
+    @Mock private ImsStateTracker mImsStateTracker;
+    @Mock private DomainSelectorBase.DestroyListener mDomainSelectorDestroyListener;
+
+    private final SelectionAttributes mSelectionAttributes =
+            new SelectionAttributes.Builder(SLOT_0, SUB_1, SELECTOR_TYPE_SMS)
+            .setEmergency(true)
+            .build();
+    private Context mContext;
+    private Looper mLooper;
+    private TestableLooper mTestableLooper;
+    private CarrierConfigManager mCarrierConfigManager;
+    private NetworkRegistrationInfo mNetworkRegistrationInfo;
+    private boolean mCarrierConfigManagerNullTest = false;
+    private BarringInfo mBarringInfo = new BarringInfo();
+    private ImsStateTracker.ImsStateListener mImsStateListener;
+    private ImsStateTracker.BarringInfoListener mBarringInfoListener;
+    private ImsStateTracker.ServiceStateListener mServiceStateListener;
+    private EmergencySmsDomainSelector mDomainSelector;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = new TestContext() {
+            @Override
+            public Object getSystemService(String name) {
+                if (name.equals(Context.CARRIER_CONFIG_SERVICE)) {
+                    if (mCarrierConfigManagerNullTest) {
+                        return null;
+                    }
+                }
+
+                return super.getSystemService(name);
+            }
+        };
+
+        HandlerThread handlerThread = new HandlerThread(
+                EmergencySmsDomainSelectorTest.class.getSimpleName());
+        handlerThread.start();
+        mLooper = handlerThread.getLooper();
+        mTestableLooper = new TestableLooper(mLooper);
+        mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
+
+        mDomainSelector = new EmergencySmsDomainSelector(mContext, SLOT_0, SUB_1,
+                mLooper, mImsStateTracker, mDomainSelectorDestroyListener);
+
+        ArgumentCaptor<ImsStateTracker.ServiceStateListener> serviceStateListenerCaptor =
+                ArgumentCaptor.forClass(ImsStateTracker.ServiceStateListener.class);
+        verify(mImsStateTracker).addServiceStateListener(serviceStateListenerCaptor.capture());
+        mServiceStateListener = serviceStateListenerCaptor.getValue();
+        assertNotNull(mServiceStateListener);
+
+        ArgumentCaptor<ImsStateTracker.BarringInfoListener> barringInfoListenerCaptor =
+                ArgumentCaptor.forClass(ImsStateTracker.BarringInfoListener.class);
+        verify(mImsStateTracker).addBarringInfoListener(barringInfoListenerCaptor.capture());
+        mBarringInfoListener = barringInfoListenerCaptor.getValue();
+        assertNotNull(mBarringInfoListener);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mTestableLooper != null) {
+            mTestableLooper.destroy();
+            mTestableLooper = null;
+        }
+
+        if (mDomainSelector != null) {
+            mDomainSelector.destroy();
+            verify(mImsStateTracker).removeImsStateListener(eq(mDomainSelector));
+            verify(mImsStateTracker).removeBarringInfoListener(eq(mDomainSelector));
+            verify(mImsStateTracker).removeServiceStateListener(eq(mDomainSelector));
+        }
+
+        if (mLooper != null) {
+            mLooper.quit();
+            mLooper = null;
+        }
+
+        mDomainSelector = null;
+        mNetworkRegistrationInfo = null;
+        mVopsSupportInfo = null;
+        mDomainSelectorDestroyListener = null;
+        mWwanSelectorCallback = null;
+        mTransportSelectorCallback = null;
+        mServiceState = null;
+        mCarrierConfigManager = null;
+        mCarrierConfigManagerNullTest = false;
+    }
+
+    @Test
+    @SmallTest
+    public void testFinishSelection() {
+        setUpImsStateTracker(AccessNetworkType.EUTRAN);
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+        mServiceStateListener.onServiceStateUpdated(mServiceState);
+        mBarringInfoListener.onBarringInfoUpdated(mBarringInfo);
+
+        assertTrue(mDomainSelector.isDomainSelectionRequested());
+        assertTrue(mDomainSelector.isDomainSelectionReady());
+
+        mDomainSelector.finishSelection();
+
+        assertFalse(mDomainSelector.isDomainSelectionReady());
+        assertFalse(mDomainSelector.isDomainSelectionRequested());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsDomainSelectionReady() {
+        setUpImsStateTracker(AccessNetworkType.EUTRAN);
+
+        assertFalse(mDomainSelector.isDomainSelectionReady());
+
+        mServiceStateListener.onServiceStateUpdated(mServiceState);
+        mBarringInfoListener.onBarringInfoUpdated(mBarringInfo);
+
+        assertTrue(mDomainSelector.isDomainSelectionReady());
+        assertFalse(mDomainSelector.isDomainSelectionRequested());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsDomainSelectionReadyAndSelectDomain() {
+        setUpImsStateTracker(AccessNetworkType.EUTRAN);
+        setUpWwanSelectorCallback();
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+        mServiceStateListener.onServiceStateUpdated(mServiceState);
+        mBarringInfoListener.onBarringInfoUpdated(mBarringInfo);
+
+        assertTrue(mDomainSelector.isDomainSelectionRequested());
+        assertTrue(mDomainSelector.isDomainSelectionReady());
+
+        processAllMessages();
+
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+        assertFalse(mDomainSelector.isDomainSelectionRequested());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsSmsOverImsAvailableWhenImsRegistered() {
+        assertFalse(mDomainSelector.isSmsOverImsAvailable());
+
+        setUpImsStateTracker(AccessNetworkType.EUTRAN);
+
+        assertTrue(mDomainSelector.isSmsOverImsAvailable());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsSmsOverImsAvailableWhenImsRegisteredAndConfigEnabledAndLteAvailable() {
+        setUpImsStateTracker(AccessNetworkType.EUTRAN);
+        setUpCarrierConfig(true);
+        setUpLteInService(false, false, true, false, false);
+
+        assertTrue(mDomainSelector.isSmsOverImsAvailable());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsSmsOverImsAvailableWhenImsRegisteredAndConfigEnabledAndLteNotAvailable() {
+        setUpImsStateTracker(AccessNetworkType.EUTRAN);
+        setUpCarrierConfig(true);
+        setUpLteInService(false, false, false, false, false);
+
+        assertFalse(mDomainSelector.isSmsOverImsAvailable());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsSmsOverImsAvailableWhenCarrierConfigManagerIsNull() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        mCarrierConfigManagerNullTest = true;
+
+        assertFalse(mDomainSelector.isSmsOverImsAvailable());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsSmsOverImsAvailableWhenCarrierConfigIsNull() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(null);
+
+        assertFalse(mDomainSelector.isSmsOverImsAvailable());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsSmsOverImsAvailableWhenCarrierConfigNotEnabled() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpCarrierConfig(false);
+
+        assertFalse(mDomainSelector.isSmsOverImsAvailable());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsSmsOverImsAvailableWhenMmTelFeatureUnavailable() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN, false, false);
+        setUpCarrierConfig(true);
+
+        assertFalse(mDomainSelector.isSmsOverImsAvailable());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsSmsOverImsAvailableWhenServiceStateIsNull() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpCarrierConfig(true);
+
+        assertFalse(mDomainSelector.isSmsOverImsAvailable());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsSmsOverImsAvailableWhenNoLte() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpCarrierConfig(true);
+        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UMTS)
+                .build();
+        when(mServiceState.getNetworkRegistrationInfo(
+                anyInt(), eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
+                .thenReturn(mNetworkRegistrationInfo);
+
+        mServiceStateListener.onServiceStateUpdated(mServiceState);
+
+        assertFalse(mDomainSelector.isSmsOverImsAvailable());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsSmsOverImsAvailableWhenLteNotRegisteredOrEmergencyNotEnabled() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpCarrierConfig(true);
+        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .build();
+        when(mServiceState.getNetworkRegistrationInfo(
+                anyInt(), eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
+                .thenReturn(mNetworkRegistrationInfo);
+
+        mServiceStateListener.onServiceStateUpdated(mServiceState);
+
+        assertFalse(mDomainSelector.isSmsOverImsAvailable());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsSmsOverImsAvailableWhenLteInServiceAndNoDataSpecificRegistrationInfo() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpCarrierConfig(true);
+        setUpLteInService(true, true, true, true, false);
+
+        assertFalse(mDomainSelector.isSmsOverImsAvailable());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsSmsOverImsAvailableWhenLteInServiceAndNoVopsSupportInfo() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpCarrierConfig(true);
+        setUpLteInService(false, true, true, true, false);
+
+        assertFalse(mDomainSelector.isSmsOverImsAvailable());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsSmsOverImsAvailableWhenLteInServiceAndEmcBsNotSupported() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpCarrierConfig(true);
+        setUpLteInService(false, false, false, true, false);
+
+        assertFalse(mDomainSelector.isSmsOverImsAvailable());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsSmsOverImsAvailableWhenLteInServiceAndEmcBsSupportedAndNoBarringInfo() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpCarrierConfig(true);
+        setUpLteInService(false, false, true, true, false);
+
+        assertTrue(mDomainSelector.isSmsOverImsAvailable());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsSmsOverImsAvailableWhenLteInServiceAndEmcBsSupportedAndBarred() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpCarrierConfig(true);
+        setUpLteInService(false, false, true, false, true);
+
+        assertFalse(mDomainSelector.isSmsOverImsAvailable());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsSmsOverImsAvailableWhenLteInServiceAndEmcBsSupportedAndNotBarred() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpCarrierConfig(true);
+        setUpLteInService(false, false, true, false, false);
+
+        assertTrue(mDomainSelector.isSmsOverImsAvailable());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsSmsOverImsAvailableWhenLteInLimitedService() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpCarrierConfig(true);
+        setUpLimitedLteService(false, false, true, false, false);
+
+        assertTrue(mDomainSelector.isSmsOverImsAvailable());
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhilePreviousRequestInProgress() {
+        setUpImsStateTracker(AccessNetworkType.EUTRAN);
+        setUpWwanSelectorCallback();
+        setUpCarrierConfig(true);
+        setUpLteInService(false, false, true, false, false);
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+        mServiceStateListener.onServiceStateUpdated(mServiceState);
+        mBarringInfoListener.onBarringInfoUpdated(mBarringInfo);
+
+        assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+        processAllMessages();
+
+        // onDomainSelected will be invoked only once
+        // even though the domain selection was requested twice.
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+        assertFalse(mDomainSelector.isDomainSelectionRequested());
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenImsNotRegisteredAndConfigDisabled() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpWwanSelectorCallback();
+        setUpCarrierConfig(false);
+        setUpLteInService(false, false, true, false, false);
+        setUpImsStateListener(true, false, false);
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+        processAllMessages();
+
+        // Expected: CS network
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenImsNotRegisteredAndUmtsNetwork() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpWwanSelectorCallback();
+        setUpCarrierConfig(true);
+        setUpNonLteService(TelephonyManager.NETWORK_TYPE_UMTS);
+        setUpImsStateListener(true, false, false);
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+        processAllMessages();
+
+        // Expected: CS network
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenImsNotRegisteredAndUnknownNetwork() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpWwanSelectorCallback();
+        setUpCarrierConfig(true);
+        setUpNonLteService(TelephonyManager.NETWORK_TYPE_UNKNOWN);
+        setUpImsStateListener(true, false, false);
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+        processAllMessages();
+
+        // Expected: CS network
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenImsNotRegisteredAndLteInService() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpWwanSelectorCallback();
+        setUpCarrierConfig(true);
+        setUpLteInService(false, false, true, false, false);
+        setUpImsStateListener(true, false, false);
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+        processAllMessages();
+
+        // Expected: PS network
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenImsNotRegisteredAndLteEmcBsNotSupported() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpWwanSelectorCallback();
+        setUpCarrierConfig(true);
+        setUpLteInService(false, false, false, false, false);
+        setUpImsStateListener(true, false, false);
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+        processAllMessages();
+
+        // Expected: CS network
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenImsNotRegisteredAndLteEmergencyBarred() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpWwanSelectorCallback();
+        setUpCarrierConfig(true);
+        setUpLteInService(false, false, true, false, true);
+        setUpImsStateListener(true, false, false);
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+        processAllMessages();
+
+        // Expected: CS network
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenImsNotRegisteredAndLimitedLteService() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpWwanSelectorCallback();
+        setUpCarrierConfig(true);
+        setUpLimitedLteService(false, false, true, false, false);
+        setUpImsStateListener(true, false, false);
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+        processAllMessages();
+
+        // Expected: PS network
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenImsNotRegisteredAndLimitedLteEmcBsNotSupported() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpWwanSelectorCallback();
+        setUpCarrierConfig(true);
+        setUpLimitedLteService(false, false, false, false, false);
+        setUpImsStateListener(true, false, false);
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+        processAllMessages();
+
+        // Expected: CS network
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenImsNotRegisteredAndLimitedLteEmergencyBarred() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpWwanSelectorCallback();
+        setUpCarrierConfig(true);
+        setUpLimitedLteService(false, false, true, false, true);
+        setUpImsStateListener(true, false, false);
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+        processAllMessages();
+
+        // Expected: CS network
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenImsRegisteredOnLte() {
+        setUpImsStateTracker(AccessNetworkType.EUTRAN);
+        setUpWwanSelectorCallback();
+        setUpCarrierConfig(true);
+        setUpLteInService(false, false, true, false, false);
+        setUpImsStateListener(true, true, true);
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+        processAllMessages();
+
+        // Expected: PS network
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenImsRegisteredOnLteAndEmcBsNotSupported() {
+        setUpImsStateTracker(AccessNetworkType.EUTRAN);
+        setUpWwanSelectorCallback();
+        setUpCarrierConfig(true);
+        setUpLteInService(false, false, false, false, false);
+        setUpImsStateListener(true, true, true);
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+        processAllMessages();
+
+        // Expected: CS network
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenImsRegisteredOnLteAndEmergencyBarred() {
+        setUpImsStateTracker(AccessNetworkType.EUTRAN);
+        setUpWwanSelectorCallback();
+        setUpCarrierConfig(true);
+        setUpLteInService(false, false, true, false, true);
+        setUpImsStateListener(true, true, true);
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+        processAllMessages();
+
+        // Expected: CS network
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenImsRegisteredOnIwlanAndConfigDisabled() {
+        setUpImsStateTracker(AccessNetworkType.IWLAN);
+        setUpWwanSelectorCallback();
+        setUpCarrierConfig(false);
+        setUpLteInService(false, false, true, false, false);
+        setUpImsStateListener(true, true, true);
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+        processAllMessages();
+
+        // Expected: WLAN
+        verify(mTransportSelectorCallback).onWlanSelected();
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenImsRegisteredOnIwlanAndLteNotAvailable() {
+        setUpImsStateTracker(AccessNetworkType.IWLAN);
+        setUpWwanSelectorCallback();
+        setUpCarrierConfig(true);
+        setUpLteInService(false, false, false, false, false);
+        setUpImsStateListener(true, true, true);
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+        processAllMessages();
+
+        // Expected: CS network - even though IMS is successfully registered over Wi-Fi,
+        // if the emergency SMS messages over IMS is enabled in the carrier configuration and
+        // the PS network does not allow the emergency service, this MO SMS should be routed to
+        // CS domain.
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenImsRegisteredOnIwlanAndLteAvailable() {
+        setUpImsStateTracker(AccessNetworkType.IWLAN);
+        setUpWwanSelectorCallback();
+        setUpCarrierConfig(true);
+        setUpLteInService(false, false, true, false, false);
+        setUpImsStateListener(true, true, true);
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+        processAllMessages();
+
+        // Expected: PS network
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+    }
+
+    private void setUpCarrierConfig(boolean supported) {
+        PersistableBundle b = new PersistableBundle();
+        b.putBoolean(CarrierConfigManager.KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL, supported);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(b);
+    }
+
+    private void setUpNonLteService(int networkType) {
+        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(networkType)
+                .setRegistrationState(networkType == TelephonyManager.NETWORK_TYPE_UNKNOWN
+                        ? NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN
+                        : NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .build();
+        when(mServiceState.getNetworkRegistrationInfo(
+                anyInt(), eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
+                .thenReturn(mNetworkRegistrationInfo);
+
+        mServiceStateListener.onServiceStateUpdated(mServiceState);
+        mBarringInfoListener.onBarringInfoUpdated(null);
+    }
+
+    private void setUpLteInService(boolean noDataSpecificRegistrationInfo,
+            boolean noVopsSupportInfo, boolean emcBsSupported,
+            boolean noBarringInfo, boolean barred) {
+        DataSpecificRegistrationInfo dsri = noDataSpecificRegistrationInfo
+                ? null : new DataSpecificRegistrationInfo(
+                        8, false, false, false, noVopsSupportInfo ? null : mVopsSupportInfo);
+
+        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .setDataSpecificInfo(dsri)
+                .build();
+        when(mServiceState.getNetworkRegistrationInfo(
+                anyInt(), eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
+                .thenReturn(mNetworkRegistrationInfo);
+        when(mVopsSupportInfo.isEmergencyServiceSupported()).thenReturn(emcBsSupported);
+
+        BarringInfo barringInfo = null;
+
+        if (!noBarringInfo) {
+            SparseArray<BarringInfo.BarringServiceInfo> barringServiceInfos = new SparseArray<>();
+            barringServiceInfos.put(BarringInfo.BARRING_SERVICE_TYPE_EMERGENCY,
+                    new BarringInfo.BarringServiceInfo(
+                            barred ? BarringInfo.BarringServiceInfo.BARRING_TYPE_UNCONDITIONAL :
+                                    BarringInfo.BarringServiceInfo.BARRING_TYPE_NONE, false, 0, 0));
+            barringInfo = new BarringInfo(null, barringServiceInfos);
+        }
+
+        mServiceStateListener.onServiceStateUpdated(mServiceState);
+        mBarringInfoListener.onBarringInfoUpdated(barringInfo);
+    }
+
+    private void setUpLimitedLteService(boolean noDataSpecificRegistrationInfo,
+            boolean noVopsSupportInfo, boolean emcBsSupported,
+            boolean noBarringInfo, boolean barred) {
+        DataSpecificRegistrationInfo dsri = noDataSpecificRegistrationInfo
+                ? null : new DataSpecificRegistrationInfo(
+                        8, false, false, false, noVopsSupportInfo ? null : mVopsSupportInfo);
+
+        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setEmergencyOnly(true)
+                .setDataSpecificInfo(dsri)
+                .build();
+        when(mServiceState.getNetworkRegistrationInfo(
+                anyInt(), eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
+                .thenReturn(mNetworkRegistrationInfo);
+        when(mVopsSupportInfo.isEmergencyServiceSupported()).thenReturn(emcBsSupported);
+
+        BarringInfo barringInfo = null;
+
+        if (!noBarringInfo) {
+            SparseArray<BarringInfo.BarringServiceInfo> barringServiceInfos = new SparseArray<>();
+            barringServiceInfos.put(BarringInfo.BARRING_SERVICE_TYPE_EMERGENCY,
+                    new BarringInfo.BarringServiceInfo(
+                            barred ? BarringInfo.BarringServiceInfo.BARRING_TYPE_UNCONDITIONAL :
+                                    BarringInfo.BarringServiceInfo.BARRING_TYPE_NONE, false, 0, 0));
+            barringInfo = new BarringInfo(null, barringServiceInfos);
+        }
+
+        mServiceStateListener.onServiceStateUpdated(mServiceState);
+        mBarringInfoListener.onBarringInfoUpdated(barringInfo);
+    }
+
+    private void setUpImsStateTracker(@RadioAccessNetworkType int accessNetworkType) {
+        setUpImsStateTracker(accessNetworkType, true, true);
+    }
+
+    private void setUpImsStateTracker(@RadioAccessNetworkType int accessNetworkType,
+            boolean mmTelFeatureAvailable, boolean smsCapable) {
+        when(mImsStateTracker.isMmTelFeatureAvailable()).thenReturn(mmTelFeatureAvailable);
+        when(mImsStateTracker.isImsRegistered())
+                .thenReturn(accessNetworkType != AccessNetworkType.UNKNOWN);
+        when(mImsStateTracker.isImsRegisteredOverWlan())
+                .thenReturn(accessNetworkType == AccessNetworkType.IWLAN);
+        when(mImsStateTracker.getImsAccessNetworkType()).thenReturn(accessNetworkType);
+        when(mImsStateTracker.isImsSmsCapable()).thenReturn(smsCapable);
+    }
+
+    private void setUpWwanSelectorCallback() {
+        doAnswer((invocation) -> {
+            Object[] args = invocation.getArguments();
+            final Consumer<WwanSelectorCallback> callback =
+                    (Consumer<WwanSelectorCallback>) args[0];
+            callback.accept(mWwanSelectorCallback);
+            return null;
+        }).when(mTransportSelectorCallback).onWwanSelected(any(Consumer.class));
+    }
+
+    private void setUpImsStateListener(boolean notifyMmTelFeatureAvailable,
+            boolean notifyImsRegState, boolean notifyMmTelCapability) {
+        doAnswer((invocation) -> {
+            Object[] args = invocation.getArguments();
+            final ImsStateTracker.ImsStateListener listener =
+                    (ImsStateTracker.ImsStateListener) args[0];
+            mDomainSelector.post(() -> {
+                if (notifyMmTelFeatureAvailable) {
+                    listener.onImsMmTelFeatureAvailableChanged();
+                }
+                if (notifyImsRegState) {
+                    listener.onImsRegistrationStateChanged();
+                }
+                if (notifyMmTelCapability) {
+                    listener.onImsMmTelCapabilitiesChanged();
+                }
+            });
+            return null;
+        }).when(mImsStateTracker).addImsStateListener(any(ImsStateTracker.ImsStateListener.class));
+    }
+
+    private void processAllMessages() {
+        while (!mTestableLooper.getLooper().getQueue().isIdle()) {
+            mTestableLooper.processAllMessages();
+        }
+    }
+}
diff --git a/tests/src/com/android/services/telephony/domainselection/SmsDomainSelectorTest.java b/tests/src/com/android/services/telephony/domainselection/SmsDomainSelectorTest.java
new file mode 100644
index 0000000..412d83d
--- /dev/null
+++ b/tests/src/com/android/services/telephony/domainselection/SmsDomainSelectorTest.java
@@ -0,0 +1,434 @@
+/*
+ * 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_SMS;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.TransportSelectorCallback;
+import android.telephony.WwanSelectorCallback;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableLooper;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.TestContext;
+
+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.function.Consumer;
+
+/**
+ * Unit tests for SmsDomainSelector.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SmsDomainSelectorTest {
+    private static final String LOG_TAG = "DomainSelector-SMS";
+    private static final int SLOT_0 = 0;
+    private static final int SUB_1 = 1;
+    private static final int SUB_2 = 2;
+
+    @Mock private TransportSelectorCallback mTransportSelectorCallback;
+    @Mock private WwanSelectorCallback mWwanSelectorCallback;
+    @Mock private ImsStateTracker mImsStateTracker;
+    @Mock private DomainSelectorBase.DestroyListener mDomainSelectorDestroyListener;
+
+    private final SelectionAttributes mSelectionAttributes =
+            new SelectionAttributes.Builder(SLOT_0, SUB_1, SELECTOR_TYPE_SMS).build();
+    private Context mContext;
+    private Looper mLooper;
+    private TestableLooper mTestableLooper;
+    private SmsDomainSelector mDomainSelector;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = new TestContext();
+        HandlerThread handlerThread = new HandlerThread(
+                SmsDomainSelectorTest.class.getSimpleName());
+        handlerThread.start();
+        mLooper = handlerThread.getLooper();
+        mTestableLooper = new TestableLooper(mLooper);
+        mDomainSelector = new SmsDomainSelector(mContext, SLOT_0, SUB_1,
+                mLooper, mImsStateTracker, mDomainSelectorDestroyListener);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mTestableLooper != null) {
+            mTestableLooper.destroy();
+            mTestableLooper = null;
+        }
+
+        if (mDomainSelector != null) {
+            mDomainSelector.destroy();
+            verify(mImsStateTracker).removeImsStateListener(eq(mDomainSelector));
+        }
+
+        if (mLooper != null) {
+            mLooper.quit();
+            mLooper = null;
+        }
+
+        mDomainSelector = null;
+        mWwanSelectorCallback = null;
+        mTransportSelectorCallback = null;
+        mImsStateTracker = null;
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenImsRegisteredOnEutran() {
+        selectDomain(AccessNetworkType.EUTRAN);
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenImsRegisteredOnNgran() {
+        selectDomain(AccessNetworkType.NGRAN);
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenImsRegisteredOnIwlan() {
+        selectDomain(AccessNetworkType.IWLAN);
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenImsNotRegistered() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpImsStateListener(true, false, false);
+        setUpWwanSelectorCallback();
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+        assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+        processAllMessages();
+
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+        assertFalse(mDomainSelector.isDomainSelectionRequested());
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhenWwanSelectorCallbackNull() {
+        setUpImsStateTracker(AccessNetworkType.EUTRAN);
+        doAnswer((invocation) -> {
+            Object[] args = invocation.getArguments();
+            final Consumer<WwanSelectorCallback> callback =
+                    (Consumer<WwanSelectorCallback>) args[0];
+            callback.accept(null);
+            return null;
+        }).when(mTransportSelectorCallback).onWwanSelected(any(Consumer.class));
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+        assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+        processAllMessages();
+
+        verify(mTransportSelectorCallback).onSelectionTerminated(anyInt());
+        assertFalse(mDomainSelector.isDomainSelectionRequested());
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWhilePreviousRequestInProgress() {
+        setUpImsStateTracker(AccessNetworkType.EUTRAN);
+        setUpWwanSelectorCallback();
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+        assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+        processAllMessages();
+
+        // onDomainSelected will be invoked only once
+        // even though the domain selection was requested twice.
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+        assertFalse(mDomainSelector.isDomainSelectionRequested());
+    }
+
+    @Test
+    @SmallTest
+    public void testCancelSelection() {
+        setUpImsStateTracker(AccessNetworkType.EUTRAN);
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+        assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+        mDomainSelector.cancelSelection();
+
+        assertFalse(mDomainSelector.isDomainSelectionRequested());
+        verify(mDomainSelectorDestroyListener).onDomainSelectorDestroyed(eq(mDomainSelector));
+    }
+
+    @Test
+    @SmallTest
+    public void testFinishSelection() {
+        setUpImsStateTracker(AccessNetworkType.EUTRAN);
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+        assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+        mDomainSelector.finishSelection();
+
+        assertFalse(mDomainSelector.isDomainSelectionRequested());
+        verify(mDomainSelectorDestroyListener).onDomainSelectorDestroyed(eq(mDomainSelector));
+    }
+
+    @Test
+    @SmallTest
+    public void testReselectDomain() {
+        setUpImsStateTracker(AccessNetworkType.EUTRAN, AccessNetworkType.IWLAN);
+        setUpWwanSelectorCallback();
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+        assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+        processAllMessages();
+
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+        assertFalse(mDomainSelector.isDomainSelectionRequested());
+
+        mDomainSelector.reselectDomain(mSelectionAttributes);
+
+        assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+        processAllMessages();
+
+        verify(mTransportSelectorCallback).onWlanSelected();
+        assertFalse(mDomainSelector.isDomainSelectionRequested());
+    }
+
+    @Test
+    @SmallTest
+    public void testReselectDomainWhilePreviousRequestInProgress() {
+        setUpImsStateTracker(AccessNetworkType.EUTRAN, AccessNetworkType.IWLAN);
+        setUpWwanSelectorCallback();
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+        assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+        mDomainSelector.reselectDomain(mSelectionAttributes);
+        processAllMessages();
+
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+        assertFalse(mDomainSelector.isDomainSelectionRequested());
+        verify(mTransportSelectorCallback, never()).onWlanSelected();
+        assertFalse(mDomainSelector.isDomainSelectionRequested());
+    }
+
+    @Test
+    @SmallTest
+    public void testOnImsRegistrationStateChangedWhenNotRegistered() {
+        setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+        setUpImsStateListener(false, true, false);
+        setUpWwanSelectorCallback();
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+        assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+        processAllMessages();
+
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+        assertFalse(mDomainSelector.isDomainSelectionRequested());
+    }
+
+    @Test
+    @SmallTest
+    public void testOnImsRegistrationStateChangedWhenRegisteredAndSmsCapable() {
+        setUpImsStateTracker(AccessNetworkType.EUTRAN, true);
+        setUpImsStateListener(false, true, false);
+        setUpWwanSelectorCallback();
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+        assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+        processAllMessages();
+
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+        assertFalse(mDomainSelector.isDomainSelectionRequested());
+    }
+
+    @Test
+    @SmallTest
+    public void testOnImsRegistrationStateChangedWhenRegisteredAndSmsIncapable() {
+        setUpImsStateTracker(AccessNetworkType.EUTRAN, false);
+        setUpImsStateListener(false, true, false);
+        setUpWwanSelectorCallback();
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+        assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+        processAllMessages();
+
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+        assertFalse(mDomainSelector.isDomainSelectionRequested());
+    }
+
+    @Test
+    @SmallTest
+    public void testOnImsMmTelCapabilitiesChangedWhenSmsCapable() {
+        setUpImsStateTracker(AccessNetworkType.EUTRAN, true);
+        setUpImsStateListener(false, false, true);
+        setUpWwanSelectorCallback();
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+        assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+        processAllMessages();
+
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+        assertFalse(mDomainSelector.isDomainSelectionRequested());
+    }
+
+    @Test
+    @SmallTest
+    public void testOnImsMmTelCapabilitiesChangedWhenSmsIncapable() {
+        setUpImsStateTracker(AccessNetworkType.EUTRAN, false);
+        setUpImsStateListener(false, false, true);
+        setUpWwanSelectorCallback();
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+        assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+        processAllMessages();
+
+        verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+        assertFalse(mDomainSelector.isDomainSelectionRequested());
+    }
+
+    private void selectDomain(@RadioAccessNetworkType int accessNetworkType) {
+        setUpImsStateTracker(accessNetworkType);
+        setUpWwanSelectorCallback();
+
+        mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+        assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+        processAllMessages();
+
+        if (accessNetworkType == AccessNetworkType.IWLAN) {
+            verify(mTransportSelectorCallback).onWlanSelected();
+        } else {
+            verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+        }
+        assertFalse(mDomainSelector.isDomainSelectionRequested());
+    }
+
+    private void setUpImsStateTracker(@RadioAccessNetworkType int accessNetworkType) {
+        setUpImsStateTracker(accessNetworkType, true);
+    }
+
+    private void setUpImsStateTracker(@RadioAccessNetworkType int accessNetworkType,
+            boolean smsCapable) {
+        when(mImsStateTracker.isMmTelFeatureAvailable()).thenReturn(true);
+        when(mImsStateTracker.isImsRegistered())
+                .thenReturn(accessNetworkType != AccessNetworkType.UNKNOWN);
+        when(mImsStateTracker.isImsRegisteredOverWlan())
+                .thenReturn(accessNetworkType == AccessNetworkType.IWLAN);
+        when(mImsStateTracker.getImsAccessNetworkType()).thenReturn(accessNetworkType);
+        when(mImsStateTracker.isImsSmsCapable()).thenReturn(smsCapable);
+    }
+
+    private void setUpImsStateTracker(@RadioAccessNetworkType int firstAccessNetworkType,
+            @RadioAccessNetworkType int secondAccessNetworkType) {
+        when(mImsStateTracker.isMmTelFeatureAvailable()).thenReturn(true);
+        when(mImsStateTracker.isImsRegistered()).thenReturn(
+                firstAccessNetworkType != AccessNetworkType.UNKNOWN,
+                secondAccessNetworkType != AccessNetworkType.UNKNOWN);
+        when(mImsStateTracker.isImsRegisteredOverWlan()).thenReturn(
+                firstAccessNetworkType == AccessNetworkType.IWLAN,
+                secondAccessNetworkType == AccessNetworkType.IWLAN);
+        when(mImsStateTracker.getImsAccessNetworkType()).thenReturn(
+                firstAccessNetworkType,
+                secondAccessNetworkType);
+        when(mImsStateTracker.isImsSmsCapable()).thenReturn(true);
+    }
+
+    private void setUpWwanSelectorCallback() {
+        doAnswer((invocation) -> {
+            Object[] args = invocation.getArguments();
+            final Consumer<WwanSelectorCallback> callback =
+                    (Consumer<WwanSelectorCallback>) args[0];
+            callback.accept(mWwanSelectorCallback);
+            return null;
+        }).when(mTransportSelectorCallback).onWwanSelected(any(Consumer.class));
+    }
+
+    private void setUpImsStateListener(boolean notifyMmTelFeatureAvailable,
+            boolean notifyImsRegState, boolean notifyMmTelCapability) {
+        doAnswer((invocation) -> {
+            Object[] args = invocation.getArguments();
+            final ImsStateTracker.ImsStateListener listener =
+                    (ImsStateTracker.ImsStateListener) args[0];
+            mDomainSelector.post(() -> {
+                if (notifyMmTelFeatureAvailable) {
+                    listener.onImsMmTelFeatureAvailableChanged();
+                }
+                if (notifyImsRegState) {
+                    listener.onImsRegistrationStateChanged();
+                }
+                if (notifyMmTelCapability) {
+                    listener.onImsMmTelCapabilitiesChanged();
+                }
+            });
+            return null;
+        }).when(mImsStateTracker).addImsStateListener(any(ImsStateTracker.ImsStateListener.class));
+    }
+
+    private void processAllMessages() {
+        while (!mTestableLooper.getLooper().getQueue().isIdle()) {
+            mTestableLooper.processAllMessages();
+        }
+    }
+}