Merge "Update handleImsUnregistered"
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index bfcc064..ede0015 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -1374,17 +1374,18 @@
         }
         for (String key : keys) {
             Objects.requireNonNull(key, "Config key must be non-null");
-            // TODO(b/261776046): validate provided key which may has no default value.
-            // For now, return empty bundle if any required key is not supported
-            if (!allConfigs.containsKey(key)) {
-                return new PersistableBundle();
-            }
         }
 
         PersistableBundle configSubset = new PersistableBundle(
                 keys.length + CONFIG_SUBSET_METADATA_KEYS.length);
         for (String carrierConfigKey : keys) {
             Object value = allConfigs.get(carrierConfigKey);
+            if (value == null) {
+                // Filter out keys without values.
+                // In history, many AOSP or OEMs/carriers private configs didn't provide default
+                // values. We have to continue supporting them for now. See b/261776046 for details.
+                continue;
+            }
             // Config value itself could be PersistableBundle which requires different API to put
             if (value instanceof PersistableBundle) {
                 configSubset.putPersistableBundle(carrierConfigKey, (PersistableBundle) value);
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 186e7b6..78a734a 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -70,6 +70,7 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.data.DataEvaluation.DataDisallowedReason;
 import com.android.internal.telephony.domainselection.DomainSelectionResolver;
+import com.android.internal.telephony.emergency.EmergencyStateTracker;
 import com.android.internal.telephony.ims.ImsResolver;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
@@ -460,6 +461,10 @@
             if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
                 mDomainSelectionService = new TelephonyDomainSelectionService(this);
                 DomainSelectionResolver.getInstance().initialize(mDomainSelectionService);
+                // Initialize EmergencyStateTracker if domain selection is supported
+                boolean isSuplDdsSwitchRequiredForEmergencyCall = getResources()
+                        .getBoolean(R.bool.config_gnss_supl_requires_default_data_for_emergency);
+                EmergencyStateTracker.make(this, isSuplDdsSwitchRequiredForEmergencyCall);
             }
 
             // Only bring up ImsResolver if the device supports having an IMS stack.
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 0124ee1..ffc67e2 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -84,6 +84,7 @@
 import android.telephony.CallForwardingInfo;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CarrierRestrictionRules;
+import android.telephony.CellBroadcastIdRange;
 import android.telephony.CellIdentity;
 import android.telephony.CellIdentityCdma;
 import android.telephony.CellIdentityGsm;
@@ -11756,4 +11757,51 @@
         }
         return simState.ordinal();
     }
-}
\ No newline at end of file
+
+    /**
+     * Get current cell broadcast ranges.
+     */
+    @Override
+    @RequiresPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS)
+    public List<CellBroadcastIdRange> getCellBroadcastIdRanges(int subId) {
+        mApp.enforceCallingPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS,
+                "getCellBroadcastIdRanges");
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return getPhone(subId).getCellBroadcastIdRanges();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Set reception of cell broadcast messages with the list of the given ranges
+     *
+     * @param ranges the list of {@link CellBroadcastIdRange} to be enabled
+     */
+    @Override
+    @RequiresPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS)
+    public void setCellBroadcastIdRanges(int subId, @NonNull List<CellBroadcastIdRange> ranges,
+            @Nullable IIntegerConsumer callback) {
+        mApp.enforceCallingPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS,
+                "setCellBroadcastIdRanges");
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            Phone phone = getPhoneFromSubId(subId);
+            if (DBG) {
+                log("setCellBroadcastIdRanges for subId :" + subId + ", phone:" + phone);
+            }
+            phone.setCellBroadcastIdRanges(ranges, result -> {
+                if (callback != null) {
+                    try {
+                        callback.accept(result);
+                    } catch (RemoteException e) {
+                        Log.w(LOG_TAG, "setCellBroadcastIdRanges: callback not available.");
+                    }
+                }
+            });
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+}
diff --git a/src/com/android/phone/slice/SlicePurchaseController.java b/src/com/android/phone/slice/SlicePurchaseController.java
index f258e2c..64261e1 100644
--- a/src/com/android/phone/slice/SlicePurchaseController.java
+++ b/src/com/android/phone/slice/SlicePurchaseController.java
@@ -956,7 +956,6 @@
         }
         int capabilityServiceType = getSliceServiceType(capability);
         for (NetworkSliceInfo sliceInfo : mSlicingConfig.getSliceInfo()) {
-            // TODO: check if TrafficDescriptor has realtime capability slice
             if (sliceInfo.getSliceServiceType() == capabilityServiceType
                     && sliceInfo.getStatus() == NetworkSliceInfo.SLICE_STATUS_ALLOWED) {
                 return true;
@@ -967,7 +966,9 @@
 
     @NetworkSliceInfo.SliceServiceType private int getSliceServiceType(
             @TelephonyManager.PremiumCapability int capability) {
-        // TODO: Implement properly -- potentially need to add new slice service types?
+        if (capability == TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY) {
+            return NetworkSliceInfo.SLICE_SERVICE_TYPE_URLLC;
+        }
         return NetworkSliceInfo.SLICE_SERVICE_TYPE_NONE;
     }
 
diff --git a/src/com/android/services/telephony/PstnIncomingCallNotifier.java b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
index d58c211..f78a9b9 100644
--- a/src/com/android/services/telephony/PstnIncomingCallNotifier.java
+++ b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
@@ -393,9 +393,8 @@
      */
     private PhoneAccountHandle findCorrectPhoneAccountHandle() {
         TelecomAccountRegistry telecomAccountRegistry = TelecomAccountRegistry.getInstance(null);
-        // Check to see if a SIM PhoneAccountHandle Exists for the Call.
-        PhoneAccountHandle handle = telecomAccountRegistry.getPhoneAccountHandleForSubId(
-                mPhone.getSubId());
+        // Check to see if a the SIM PhoneAccountHandle Exists for the Call.
+        PhoneAccountHandle handle = PhoneUtils.makePstnPhoneAccountHandle(mPhone);
         if (telecomAccountRegistry.hasAccountEntryForPhoneAccount(handle)) {
             return handle;
         }
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/phone/slice/SlicePurchaseControllerTest.java b/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
index e9e23f3..8320807 100644
--- a/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
+++ b/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
@@ -505,7 +505,7 @@
     public void testPurchasePremiumCapabilityResultAlreadyPurchased() {
         testPurchasePremiumCapabilityResultSuccess();
 
-        sendNetworkSlicingConfig(true);
+        sendNetworkSlicingConfig(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, true);
 
         mSlicePurchaseController.purchasePremiumCapability(
                 TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, TAG,
@@ -523,7 +523,7 @@
                 mResult);
 
         // retry to verify purchase expired
-        sendNetworkSlicingConfig(false);
+        sendNetworkSlicingConfig(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, false);
 
         testPurchasePremiumCapabilityResultSuccess();
     }
@@ -705,9 +705,9 @@
         assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS, mResult);
 
         // complete network setup
-        sendNetworkSlicingConfig(true);
+        sendNetworkSlicingConfig(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, true);
         // purchase expired
-        sendNetworkSlicingConfig(false);
+        sendNetworkSlicingConfig(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, false);
     }
 
     private void sendValidPurchaseRequest() {
@@ -755,13 +755,17 @@
         verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
     }
 
-    private void sendNetworkSlicingConfig(boolean configExists) {
-        // TODO: implement slicing config logic properly
+    private void sendNetworkSlicingConfig(int capability, boolean configActive) {
+        int sliceServiceType = capability == TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY
+                ? NetworkSliceInfo.SLICE_SERVICE_TYPE_URLLC
+                : NetworkSliceInfo.SLICE_SERVICE_TYPE_NONE;
+        NetworkSliceInfo sliceInfo = new NetworkSliceInfo.Builder()
+                .setStatus(configActive ? NetworkSliceInfo.SLICE_STATUS_ALLOWED
+                        : NetworkSliceInfo.SLICE_STATUS_UNKNOWN)
+                .setSliceServiceType(sliceServiceType)
+                .build();
         NetworkSlicingConfig slicingConfig = new NetworkSlicingConfig(Collections.emptyList(),
-                configExists
-                        ? Collections.singletonList(new NetworkSliceInfo.Builder()
-                                .setStatus(NetworkSliceInfo.SLICE_STATUS_ALLOWED).build())
-                        : Collections.emptyList());
+                Collections.singletonList(sliceInfo));
         mSlicePurchaseController.obtainMessage(2 /* EVENT_SLICING_CONFIG_CHANGED */,
                 new AsyncResult(null, slicingConfig, null)).sendToTarget();
         mTestableLooper.processAllMessages();
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();
+        }
+    }
+}