Add EmergencyCallDomainSelector

Bug: 243878608
Test: atest EmergencyCallDomainSelectorTest
Change-Id: Idfba589f44de18ee8497ceacb245f4942db686c9
diff --git a/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java b/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
new file mode 100644
index 0000000..9aaf6da
--- /dev/null
+++ b/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
@@ -0,0 +1,1047 @@
+/*
+ * 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.AccessNetworkConstants.AccessNetworkType.CDMA2000;
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.EUTRAN;
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.NGRAN;
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.UNKNOWN;
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.UTRAN;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.BarringInfo.BARRING_SERVICE_TYPE_EMERGENCY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.DOMAIN_CS;
+import static android.telephony.CarrierConfigManager.ImsEmergency.DOMAIN_PS_3GPP;
+import static android.telephony.CarrierConfigManager.ImsEmergency.DOMAIN_PS_NON_3GPP;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_SCAN_TIMER_SEC_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.PowerManager;
+import android.os.SystemProperties;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.AccessNetworkConstants.TransportType;
+import android.telephony.BarringInfo;
+import android.telephony.CarrierConfigManager;
+import android.telephony.DomainSelectionService;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.EmergencyRegResult;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.TransportSelectorCallback;
+import android.telephony.ims.ImsManager;
+import android.telephony.ims.ImsMmTelManager;
+import android.text.TextUtils;
+import android.util.LocalLog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.IntFunction;
+
+/**
+ * Selects the domain for emergency calling.
+ */
+public class EmergencyCallDomainSelector extends DomainSelectorBase
+        implements ImsStateTracker.BarringInfoListener, ImsStateTracker.ImsStateListener {
+    private static final String TAG = "DomainSelector-EmergencyCall";
+    private static final boolean DBG = (SystemProperties.getInt("ro.debuggable", 0) == 1);
+    private static final int LOG_SIZE = 50;
+
+    private static final int MSG_START_DOMAIN_SELECTION = 11;
+    @VisibleForTesting
+    public static final int MSG_NETWORK_SCAN_TIMEOUT = 12;
+    private static final int MSG_NETWORK_SCAN_RESULT = 13;
+
+    private static final int NOT_SUPPORTED = -1;
+
+    private static final LocalLog sLocalLog = new LocalLog(LOG_SIZE);
+
+    private boolean mIsEmergencyBarred;
+    private boolean mImsRegistered;
+    private boolean mIsVoiceCapable;
+    private boolean mBarringInfoReceived;
+    private boolean mImsRegStateReceived;
+    private boolean mMmTelCapabilitiesReceived;
+    private int mVoWifiTrialCount = 0;
+
+    private @RadioAccessNetworkType int mCsNetworkType = UNKNOWN;
+    private @RadioAccessNetworkType int mPsNetworkType = UNKNOWN;
+    private @RadioAccessNetworkType int mLastNetworkType = UNKNOWN;
+    private @TransportType int mLastTransportType = TRANSPORT_TYPE_INVALID;
+    private @DomainSelectionService.EmergencyScanType int mScanType;
+    private @RadioAccessNetworkType List<Integer> mLastPreferredNetworks;
+
+    private CancellationSignal mCancelSignal;
+
+    private @RadioAccessNetworkType int[] mImsRatsConfig;
+    private @RadioAccessNetworkType int[] mCsRatsConfig;
+    private @RadioAccessNetworkType int[] mImsRoamRatsConfig;
+    private @RadioAccessNetworkType int[] mCsRoamRatsConfig;
+    private @CarrierConfigManager.ImsEmergency.EmergencyDomain int[] mDomainPreference;
+    private @CarrierConfigManager.ImsEmergency.EmergencyDomain int[] mDomainPreferenceRoam;
+    private List<String> mCdmaPreferredNumbers;
+    private boolean mPreferImsWhenCallsOnCs;
+    private int mScanTimeout;
+    private int mMaxNumOfVoWifiTries;
+    private @CarrierConfigManager.ImsEmergency.EmergencyScanType int mPreferredNetworkScanType;
+    private int mCallSetupTimerOnCurrentRatSec;
+    private boolean mRequiresImsRegistration;
+    private boolean mRequiresVoLteEnabled;
+    private boolean mLtePreferredAfterNrFailure;
+    private boolean mTryCsWhenPsFails;
+
+    /** Indicates whether this instance is deactivated. */
+    private boolean mDestroyed = false;
+    /** Indicates whether emergency network scan is requested. */
+    private boolean mIsScanRequested = false;
+    /** Indicates whether selected domain has been notified. */
+    private boolean mDomainSelected = false;
+    /**
+     * Indicates whether {@link #selectDomain(SelectionAttributes, TransportSelectionCallback)}
+     * is called or not.
+     */
+    private boolean mDomainSelectionRequested = false;
+
+    private final PowerManager.WakeLock mPartialWakeLock;
+
+    /** Constructor. */
+    public EmergencyCallDomainSelector(Context context, int slotId, int subId,
+            @NonNull Looper looper, @NonNull ImsStateTracker imsStateTracker,
+            @NonNull DestroyListener destroyListener) {
+        super(context, slotId, subId, looper, imsStateTracker, destroyListener, TAG);
+
+        mImsStateTracker.addBarringInfoListener(this);
+        mImsStateTracker.addImsStateListener(this);
+
+        PowerManager pm = context.getSystemService(PowerManager.class);
+        mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+
+        acquireWakeLock();
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        if (mDestroyed) return;
+
+        switch(msg.what) {
+            case MSG_START_DOMAIN_SELECTION:
+                startDomainSelection();
+                break;
+
+            case MSG_NETWORK_SCAN_TIMEOUT:
+                handleNetworkScanTimeout();
+                break;
+
+            case MSG_NETWORK_SCAN_RESULT:
+                handleScanResult((EmergencyRegResult) msg.obj);
+                break;
+
+            default:
+                super.handleMessage(msg);
+                break;
+        }
+    }
+
+    /**
+     * Handles the scan result.
+     *
+     * @param result The scan result.
+     */
+    private void handleScanResult(EmergencyRegResult result) {
+        logi("handleScanResult result=" + result);
+
+        if (result.getAccessNetwork() == UNKNOWN) {
+            if ((mPreferredNetworkScanType == SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE)
+                      || (mScanType == DomainSelectionService.SCAN_TYPE_FULL_SERVICE)) {
+                mScanType = DomainSelectionService.SCAN_TYPE_LIMITED_SERVICE;
+                mWwanSelectorCallback.onRequestEmergencyNetworkScan(
+                        mLastPreferredNetworks, mScanType, mCancelSignal,
+                        (regResult) -> {
+                            logi("requestScan-onComplete");
+                            sendMessage(obtainMessage(MSG_NETWORK_SCAN_RESULT, regResult));
+                        });
+            } else {
+                // Continuous scan, do not start a new timer.
+                requestScan(false);
+            }
+            return;
+        }
+
+        removeMessages(MSG_NETWORK_SCAN_TIMEOUT);
+        onWwanNetworkTypeSelected(result.getAccessNetwork());
+        mIsScanRequested = false;
+    }
+
+    @Override
+    public void cancelSelection() {
+        logi("cancelSelection");
+        finishSelection();
+    }
+
+    @Override
+    public void reselectDomain(SelectionAttributes attr) {
+        logi("reselectDomain tryCsWhenPsFails=" + mTryCsWhenPsFails + ", attr=" + attr);
+        mSelectionAttributes = attr;
+        if (mTryCsWhenPsFails) {
+            mTryCsWhenPsFails = false;
+            mCsNetworkType = getSelectableCsNetworkType();
+            logi("reselectDomain tryCs=" + accessNetworkTypeToString(mCsNetworkType));
+            if (mCsNetworkType != UNKNOWN) {
+                onWwanNetworkTypeSelected(mCsNetworkType);
+                return;
+            }
+        } else if (getImsNetworkTypeConfiguration().isEmpty()
+                || (mRequiresVoLteEnabled && !isAdvancedCallingSettingEnabled())) {
+            // Emergency call over IMS is not supported.
+            mCsNetworkType = UTRAN;
+            onWwanNetworkTypeSelected(mCsNetworkType);
+            return;
+        }
+
+        if (mLastTransportType == TRANSPORT_TYPE_WLAN) {
+            // Dialing over Wi-Fi failed. Try scanning cellular networks.
+            onWwanSelected(() -> {
+                requestScan(true, false, true);
+                mDomainSelected = false;
+            });
+            return;
+        }
+
+        requestScan(true);
+        mDomainSelected = false;
+    }
+
+    @Override
+    public void finishSelection() {
+        logi("finishSelection");
+        destroy();
+    }
+
+    @Override
+    public void onBarringInfoUpdated(BarringInfo barringInfo) {
+        if (mDestroyed) return;
+
+        mBarringInfoReceived = true;
+        BarringInfo.BarringServiceInfo serviceInfo =
+                barringInfo.getBarringServiceInfo(BARRING_SERVICE_TYPE_EMERGENCY);
+        mIsEmergencyBarred = serviceInfo.isBarred();
+        logi("onBarringInfoUpdated emergencyBarred=" + mIsEmergencyBarred
+                + ", serviceInfo=" + serviceInfo);
+        selectDomain();
+    }
+
+    @Override
+    public void selectDomain(SelectionAttributes attr, TransportSelectorCallback cb) {
+        logi("selectDomain attr=" + attr);
+        mTransportSelectorCallback = cb;
+        mSelectionAttributes = attr;
+
+        sendEmptyMessage(MSG_START_DOMAIN_SELECTION);
+    }
+
+    private void startDomainSelection() {
+        logi("startDomainSelection");
+        updateCarrierConfiguration();
+        mDomainSelectionRequested = true;
+        if (SubscriptionManager.isValidSubscriptionId(getSubId())) {
+            selectDomain();
+        } else {
+            logi("startDomainSelection invalid subId");
+            onImsRegistrationStateChanged();
+            onImsMmTelCapabilitiesChanged();
+        }
+    }
+
+    @Override
+    public void onImsMmTelFeatureAvailableChanged() {
+        // DOMAIN_CS shall be selected when ImsService is not available.
+        // TODO(b/258289015) Recover the temporary failure in ImsService connection.
+    }
+
+    @Override
+    public void onImsRegistrationStateChanged() {
+        mImsRegStateReceived = true;
+        mImsRegistered = mImsStateTracker.isImsRegistered();
+        logi("onImsRegistrationStateChanged " + mImsRegistered);
+        selectDomain();
+    }
+
+    @Override
+    public void onImsMmTelCapabilitiesChanged() {
+        mMmTelCapabilitiesReceived = true;
+        mIsVoiceCapable = mImsStateTracker.isImsVoiceCapable();
+        logi("onImsMmTelCapabilitiesChanged " + mIsVoiceCapable);
+        selectDomain();
+    }
+
+    /**
+     * Caches the configuration.
+     */
+    private void updateCarrierConfiguration() {
+        CarrierConfigManager configMgr = mContext.getSystemService(CarrierConfigManager.class);
+        PersistableBundle b = configMgr.getConfigForSubId(getSubId());
+        if (b == null) {
+            b = CarrierConfigManager.getDefaultConfig();
+        }
+
+        mImsRatsConfig =
+                b.getIntArray(KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY);
+        mImsRoamRatsConfig = b.getIntArray(
+                KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY);
+        if (!SubscriptionManager.isValidSubscriptionId(getSubId())) {
+            // Default configuration includes only EUTRAN . In case of no SIM, add NGRAN.
+            mImsRatsConfig = new int[] { EUTRAN, NGRAN };
+            mImsRoamRatsConfig = new int[] { EUTRAN, NGRAN };
+        }
+
+        mCsRatsConfig =
+                b.getIntArray(KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY);
+        mCsRoamRatsConfig = b.getIntArray(
+                KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY);
+        mDomainPreference = b.getIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY);
+        mDomainPreferenceRoam = b.getIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY);
+        mPreferImsWhenCallsOnCs = b.getBoolean(
+                KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL);
+        mScanTimeout = b.getInt(KEY_EMERGENCY_SCAN_TIMER_SEC_INT);
+        mMaxNumOfVoWifiTries = b.getInt(KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT);
+        mPreferredNetworkScanType = b.getInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT);
+        mCallSetupTimerOnCurrentRatSec = b.getInt(
+                KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT);
+        mRequiresImsRegistration = b.getBoolean(KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL);
+        mRequiresVoLteEnabled = b.getBoolean(KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL);
+        mLtePreferredAfterNrFailure = b.getBoolean(
+                KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL);
+        String[] numbers = b.getStringArray(KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY);
+
+        if (mImsRatsConfig == null) mImsRatsConfig = new int[0];
+        if (mCsRatsConfig == null) mCsRatsConfig = new int[0];
+        if (mImsRoamRatsConfig == null) mImsRoamRatsConfig = new int[0];
+        if (mCsRoamRatsConfig == null) mCsRoamRatsConfig = new int[0];
+        if (mDomainPreference == null) mDomainPreference = new int[0];
+        if (mDomainPreferenceRoam == null) mDomainPreferenceRoam = new int[0];
+        if (numbers == null) numbers = new String[0];
+
+        logi("updateCarrierConfiguration "
+                + "imsRats=" + arrayToString(mImsRatsConfig,
+                        EmergencyCallDomainSelector::accessNetworkTypeToString)
+                + ", csRats=" + arrayToString(mCsRatsConfig,
+                        EmergencyCallDomainSelector::accessNetworkTypeToString)
+                + ", imsRoamRats=" + arrayToString(mImsRoamRatsConfig,
+                        EmergencyCallDomainSelector::accessNetworkTypeToString)
+                + ", csRoamRats=" + arrayToString(mCsRoamRatsConfig,
+                        EmergencyCallDomainSelector::accessNetworkTypeToString)
+                + ", domainPref=" + arrayToString(mDomainPreference,
+                        EmergencyCallDomainSelector::domainPreferenceToString)
+                + ", domainPrefRoam=" + arrayToString(mDomainPreferenceRoam,
+                        EmergencyCallDomainSelector::domainPreferenceToString)
+                + ", preferImsOnCs=" + mPreferImsWhenCallsOnCs
+                + ", scanTimeout=" + mScanTimeout
+                + ", maxNumOfVoWifiTries=" + mMaxNumOfVoWifiTries
+                + ", preferredScanType=" + carrierConfigNetworkScanTypeToString(
+                        mPreferredNetworkScanType)
+                + ", callSetupTimer=" + mCallSetupTimerOnCurrentRatSec
+                + ", requiresImsReg=" + mRequiresImsRegistration
+                + ", requiresVoLteEnabled=" + mRequiresVoLteEnabled
+                + ", ltePreferredAfterNr=" + mLtePreferredAfterNrFailure
+                + ", cdmaPreferredNumbers=" + arrayToString(numbers));
+
+        mCdmaPreferredNumbers = Arrays.asList(numbers);
+
+        if ((mPreferredNetworkScanType == CarrierConfigManager.ImsEmergency.SCAN_TYPE_FULL_SERVICE)
+                || (mPreferredNetworkScanType
+                        == SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE)) {
+            mScanType = DomainSelectionService.SCAN_TYPE_FULL_SERVICE;
+        } else {
+            mScanType = DomainSelectionService.SCAN_TYPE_NO_PREFERENCE;
+        }
+    }
+
+    private void selectDomain() {
+        // State updated right after creation.
+        if (!mDomainSelectionRequested) return;
+
+        // Emergency network scan requested has not been completed.
+        if (mIsScanRequested) return;
+
+        // Domain selection completed, {@link #reselectDomain()} will restart domain selection.
+        if (mDomainSelected) return;
+
+        if (!mBarringInfoReceived || !mImsRegStateReceived || !mMmTelCapabilitiesReceived) {
+            logi("selectDomain not received"
+                    + " BarringInfo, IMS registration state, or MMTEL capabilities");
+            return;
+        }
+
+        if (isWifiPreferred()) {
+            onWlanSelected();
+            return;
+        }
+
+        onWwanSelected(this::selectDomainInternal);
+    }
+
+    private void selectDomainInternal() {
+        if (getImsNetworkTypeConfiguration().isEmpty()
+                || (mRequiresVoLteEnabled && !isAdvancedCallingSettingEnabled())) {
+            // Emergency call over IMS is not supported.
+            mCsNetworkType = UTRAN;
+            onWwanNetworkTypeSelected(mCsNetworkType);
+            return;
+        }
+
+        boolean csInService = isCsInService();
+        boolean psInService = isPsInService();
+
+        if (!csInService && !psInService) {
+            mPsNetworkType = getSelectablePsNetworkType(false);
+            logi("selectDomain limited service ps=" + accessNetworkTypeToString(mPsNetworkType));
+            if (mPsNetworkType == UNKNOWN) {
+                requestScan(true);
+            } else {
+                onWwanNetworkTypeSelected(mPsNetworkType);
+            }
+            return;
+        }
+
+        // Domain selection per 3GPP TS 23.167 Table H.1.
+        // PS is preferred in case selection between CS and PS is implementation option.
+        mCsNetworkType = UNKNOWN;
+        mPsNetworkType = UNKNOWN;
+        if (csInService) mCsNetworkType = getSelectableCsNetworkType();
+        if (psInService) mPsNetworkType = getSelectablePsNetworkType(true);
+
+        boolean csAvailable = mCsNetworkType != UNKNOWN;
+        boolean psAvailable = mPsNetworkType != UNKNOWN;
+
+        logi("selectDomain CS={" + csInService + ", " + accessNetworkTypeToString(mCsNetworkType)
+                + "}, PS={" + psInService + ", " + accessNetworkTypeToString(mPsNetworkType) + "}");
+        if (csAvailable && psAvailable) {
+            if (mPreferImsWhenCallsOnCs || isImsRegisteredWithVoiceCapability()) {
+                mTryCsWhenPsFails = true;
+                onWwanNetworkTypeSelected(mPsNetworkType);
+            } else if (isDeactivatedSim()) {
+                // Deactivated SIM but PS is in service and supports emergency calls.
+                onWwanNetworkTypeSelected(mPsNetworkType);
+            } else {
+                onWwanNetworkTypeSelected(mCsNetworkType);
+            }
+        } else if (psAvailable) {
+            if (!mRequiresImsRegistration || isImsRegisteredWithVoiceCapability()) {
+                onWwanNetworkTypeSelected(mPsNetworkType);
+            } else if (isDeactivatedSim()) {
+                // Deactivated SIM but PS is in service and supports emergency calls.
+                onWwanNetworkTypeSelected(mPsNetworkType);
+            } else {
+                // Carrier configuration requires IMS registration for emergency services over PS,
+                // but not registered. Try CS emergency call.
+                requestScan(true, true);
+            }
+        } else if (csAvailable) {
+            onWwanNetworkTypeSelected(mCsNetworkType);
+        } else {
+            // PS is in service but not supports emergency calls.
+            if (mRequiresImsRegistration && !isImsRegisteredWithVoiceCapability()) {
+                // Carrier configuration requires IMS registration for emergency services over PS,
+                // but not registered. Try CS emergency call.
+                requestScan(true, true);
+            } else {
+                requestScan(true);
+            }
+        }
+    }
+
+    /**
+     * Requests network scan.
+     *
+     * @param startVoWifiTimer Indicates whether a VoWifi timer will be started.
+     */
+    private void requestScan(boolean startVoWifiTimer) {
+        requestScan(startVoWifiTimer, false);
+    }
+
+    /**
+     * Requests network scan.
+     *
+     * @param startVoWifiTimer Indicates whether a VoWifi timer will be started.
+     * @param csPreferred Indicates whether CS preferred scan is requested.
+     */
+    private void requestScan(boolean startVoWifiTimer, boolean csPreferred) {
+        requestScan(startVoWifiTimer, csPreferred, false);
+    }
+
+    /**
+     * Requests network scan.
+     *
+     * @param startVoWifiTimer Indicates whether a VoWifi timer will be started.
+     * @param csPreferred Indicates whether CS preferred scan is requested.
+     * @param wifiFailed Indicates dialing over Wi-Fi has failed.
+     */
+    private void requestScan(boolean startVoWifiTimer, boolean csPreferred, boolean wifiFailed) {
+        logi("requestScan timer=" + startVoWifiTimer + ", csPreferred=" + csPreferred
+                + ", wifiFailed=" + wifiFailed);
+
+        mCancelSignal = new CancellationSignal();
+        // In case dialing over Wi-Fi has failed, do not the change the domain preference.
+        if (!wifiFailed) mLastPreferredNetworks = getNextPreferredNetworks(csPreferred);
+
+        if (isInRoaming()
+                && (mPreferredNetworkScanType == DomainSelectionService.SCAN_TYPE_FULL_SERVICE)) {
+            // FULL_SERVICE only preference is available only when not in roaming.
+            mScanType = DomainSelectionService.SCAN_TYPE_NO_PREFERENCE;
+        }
+
+        mIsScanRequested = true;
+        mWwanSelectorCallback.onRequestEmergencyNetworkScan(
+                mLastPreferredNetworks, mScanType, mCancelSignal,
+                (result) -> {
+                    logi("requestScan-onComplete");
+                    sendMessage(obtainMessage(MSG_NETWORK_SCAN_RESULT, result));
+                });
+
+        if (startVoWifiTimer && SubscriptionManager.isValidSubscriptionId(getSubId())) {
+            if (isEmcOverWifiSupported()
+                    && mScanTimeout > 0 && mVoWifiTrialCount < mMaxNumOfVoWifiTries) {
+                logi("requestScan start scan timer");
+                // remove any pending timers.
+                removeMessages(MSG_NETWORK_SCAN_TIMEOUT);
+                sendEmptyMessageDelayed(MSG_NETWORK_SCAN_TIMEOUT, mScanTimeout);
+            }
+        }
+    }
+
+    /**
+     * Gets the list of preferred network type for the new scan request.
+     *
+     * @param csPreferred Indicates whether CS preferred scan is requested.
+     * @return The list of preferred network types.
+     */
+    private @RadioAccessNetworkType List<Integer> getNextPreferredNetworks(boolean csPreferred) {
+        List<Integer> preferredNetworks = new ArrayList<>();
+
+        List<Integer> domains = getDomainPreference();
+        int psPriority = domains.indexOf(DOMAIN_PS_3GPP);
+        int csPriority = domains.indexOf(DOMAIN_CS);
+        logi("getNextPreferredNetworks psPriority=" + psPriority + ", csPriority=" + csPriority
+                + ", csPreferred=" + csPreferred
+                + ", lastNetworkType=" + accessNetworkTypeToString(mLastNetworkType));
+
+        if (!csPreferred && mLastNetworkType == UNKNOWN) {
+            // Generate the list per the domain preference.
+
+            if (psPriority == NOT_SUPPORTED && csPriority == NOT_SUPPORTED) {
+                // should not reach here.
+            } else if (psPriority == NOT_SUPPORTED && csPriority > NOT_SUPPORTED) {
+                // CS networks only.
+                preferredNetworks = generatePreferredNetworks(getCsNetworkTypeConfiguration());
+            } else if (psPriority > NOT_SUPPORTED && csPriority == NOT_SUPPORTED) {
+                // PS networks only.
+                preferredNetworks = generatePreferredNetworks(getImsNetworkTypeConfiguration());
+            } else if (psPriority < csPriority) {
+                // PS preferred.
+                preferredNetworks = generatePreferredNetworks(getImsNetworkTypeConfiguration(),
+                        getCsNetworkTypeConfiguration());
+            } else {
+                // CS preferred.
+                generatePreferredNetworks(getCsNetworkTypeConfiguration(),
+                        getImsNetworkTypeConfiguration());
+            }
+        } else if (csPreferred || mLastNetworkType == EUTRAN || mLastNetworkType == NGRAN) {
+            if (!csPreferred && mLastNetworkType == NGRAN && mLtePreferredAfterNrFailure) {
+                // LTE is preferred after dialing over NR failed.
+                List<Integer> imsRats = getImsNetworkTypeConfiguration();
+                imsRats.remove(new Integer(NGRAN));
+                preferredNetworks = generatePreferredNetworks(imsRats,
+                        getCsNetworkTypeConfiguration());
+            } else  if (csPriority > NOT_SUPPORTED) {
+                // PS tried, generate the list with CS preferred.
+                preferredNetworks = generatePreferredNetworks(getCsNetworkTypeConfiguration(),
+                        getImsNetworkTypeConfiguration());
+            } else {
+                // CS not suppored.
+                generatePreferredNetworks(getImsNetworkTypeConfiguration());
+            }
+        } else {
+            // CS tried, generate the list with PS preferred.
+            if (psPriority > NOT_SUPPORTED) {
+                preferredNetworks = generatePreferredNetworks(getImsNetworkTypeConfiguration(),
+                        getCsNetworkTypeConfiguration());
+            } else {
+                // PS not suppored.
+                preferredNetworks = generatePreferredNetworks(getCsNetworkTypeConfiguration());
+            }
+        }
+
+        return preferredNetworks;
+    }
+
+    private @RadioAccessNetworkType List<Integer> generatePreferredNetworks(List<Integer>...lists) {
+        List<Integer> preferredNetworks = new ArrayList<>();
+        for (List<Integer> list : lists) {
+            preferredNetworks.addAll(list);
+        }
+
+        return preferredNetworks;
+    }
+
+    private void handleNetworkScanTimeout() {
+        if (isImsRegisteredWithVoiceCapability()
+                && isImsRegisteredOverWifi()) {
+            if (mCancelSignal != null) {
+                mCancelSignal.cancel();
+                mCancelSignal = null;
+            }
+            onWlanSelected();
+        }
+    }
+
+    /**
+     * Determines whether CS is in service.
+     *
+     * @return {@code true} if CS is in service.
+     */
+    private boolean isCsInService() {
+        EmergencyRegResult regResult = mSelectionAttributes.getEmergencyRegResult();
+        if (regResult == null) return false;
+
+        int regState = regResult.getRegState();
+        int domain = regResult.getDomain();
+
+        if ((regState == REGISTRATION_STATE_HOME || regState == REGISTRATION_STATE_ROAMING)
+                && ((domain & NetworkRegistrationInfo.DOMAIN_CS) > 0)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Determines the network type of the circuit-switched(CS) network.
+     *
+     * @return The network type of the CS network.
+     */
+    private @RadioAccessNetworkType int getSelectableCsNetworkType() {
+        EmergencyRegResult regResult = mSelectionAttributes.getEmergencyRegResult();
+        logi("getSelectableCsNetworkType regResult=" + regResult);
+        if (regResult == null) return UNKNOWN;
+
+        int accessNetwork = regResult.getAccessNetwork();
+
+        List<Integer> ratList = getCsNetworkTypeConfiguration();
+        if (ratList.contains(accessNetwork)) {
+            return accessNetwork;
+        }
+
+        if ((regResult.getAccessNetwork() == EUTRAN)
+                && ((regResult.getDomain() & NetworkRegistrationInfo.DOMAIN_CS) > 0)) {
+            return UTRAN;
+        }
+
+        return UNKNOWN;
+    }
+
+    /**
+     * Determines whether PS is in service.
+     *
+     * @return {@code true} if PS is in service.
+     */
+    private boolean isPsInService() {
+        EmergencyRegResult regResult = mSelectionAttributes.getEmergencyRegResult();
+        if (regResult == null) return false;
+
+        int regState = regResult.getRegState();
+        int domain = regResult.getDomain();
+
+        if ((regState == REGISTRATION_STATE_HOME || regState == REGISTRATION_STATE_ROAMING)
+                && ((domain & NetworkRegistrationInfo.DOMAIN_PS) > 0)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Determines the network type supporting emergency services over packet-switched(PS) network.
+     *
+     * @param inService Indicates whether PS is IN_SERVICE state.
+     * @return The network type if the network supports emergency services over PS network.
+     */
+    private @RadioAccessNetworkType int getSelectablePsNetworkType(boolean inService) {
+        EmergencyRegResult regResult = mSelectionAttributes.getEmergencyRegResult();
+        logi("getSelectablePsNetworkType regResult=" + regResult);
+        if (regResult == null) return UNKNOWN;
+
+        int accessNetwork = regResult.getAccessNetwork();
+        List<Integer> ratList = getImsNetworkTypeConfiguration();
+        if (ratList.contains(accessNetwork)) {
+            if (mIsEmergencyBarred) {
+                logi("sgetSelectablePsNetworkType barred");
+                return UNKNOWN;
+            }
+            if (accessNetwork == NGRAN) {
+                return (regResult.getNwProvidedEmc() > 0 && regResult.isVopsSupported())
+                        ? NGRAN : UNKNOWN;
+            } else if (accessNetwork == EUTRAN) {
+                return (regResult.isEmcBearerSupported()
+                                && (regResult.isVopsSupported() || !inService))
+                        ? EUTRAN : UNKNOWN;
+            }
+        }
+
+        return UNKNOWN;
+    }
+
+    /**
+     * Determines whether the SIM is a deactivated one.
+     *
+     * @return {@code true} if the SIM is a deactivated one.
+     */
+    private boolean isDeactivatedSim() {
+        if (SubscriptionManager.isValidSubscriptionId(getSubId())) {
+            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+            tm = tm.createForSubscriptionId(getSubId());
+            int state = tm.getDataActivationState();
+            logi("isDeactivatedSim state=" + state);
+            return (state == TelephonyManager.SIM_ACTIVATION_STATE_DEACTIVATED);
+        }
+        return false;
+    }
+
+    /**
+     * Determines whether emergency call over Wi-Fi is allowed.
+     *
+     * @return {@code true} if emergency call over Wi-Fi allowed.
+     */
+    private boolean isEmcOverWifiSupported() {
+        if (SubscriptionManager.isValidSubscriptionId(getSubId())) {
+            List<Integer> domains = getDomainPreference();
+            return domains.contains(DOMAIN_PS_NON_3GPP);
+        }
+        return false;
+    }
+
+    /**
+     * Determines whether Wi-Fi is preferred when IMS registered over Wi-Fi.
+     *
+     * @return {@code true} if Wi-Fi is preferred when IMS registered over Wi-Fi.
+     */
+    private boolean isWifiPreferred() {
+        if (SubscriptionManager.isValidSubscriptionId(getSubId())) {
+            List<Integer> domains = getDomainPreference();
+            int priority = domains.indexOf(DOMAIN_PS_NON_3GPP);
+            logi("isWifiPreferred priority=" + priority);
+
+            if ((priority == 0)
+                    && isImsRegisteredWithVoiceCapability()
+                    && isImsRegisteredOverWifi()) {
+                logi("isWifiPreferred try emergency call over Wi-Fi");
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private boolean isAdvancedCallingSettingEnabled() {
+        try {
+            if (SubscriptionManager.isValidSubscriptionId(getSubId())) {
+                ImsManager imsMngr = mContext.getSystemService(ImsManager.class);
+                ImsMmTelManager mmTelManager = imsMngr.getImsMmTelManager(getSubId());
+                boolean result = mmTelManager.isAdvancedCallingSettingEnabled();
+                logi("isAdvancedCallingSettingEnabled " + result);
+                return result;
+            }
+        } catch (Exception e) {
+            logi("isAdvancedCallingSettingEnabled e=" + e);
+        }
+        return true;
+    }
+
+    private @NonNull List<Integer> getImsNetworkTypeConfiguration() {
+        int[] rats = mImsRatsConfig;
+        if (isInRoaming()) rats = mImsRoamRatsConfig;
+
+        List<Integer> ratList = new ArrayList<Integer>();
+        for (int i = 0; i < rats.length; i++) {
+            ratList.add(rats[i]);
+        }
+        return ratList;
+    }
+
+    private @NonNull List<Integer> getCsNetworkTypeConfiguration() {
+        int[] rats = mCsRatsConfig;
+        if (isInRoaming()) rats = mCsRoamRatsConfig;
+
+        List<Integer> ratList = new ArrayList<Integer>();
+        for (int i = 0; i < rats.length; i++) {
+            ratList.add(rats[i]);
+        }
+
+        if (!mCdmaPreferredNumbers.isEmpty()) {
+            if (mCdmaPreferredNumbers.contains(mSelectionAttributes.getNumber())) {
+                // The number will be dialed over CDMA.
+                ratList.clear();
+                ratList.add(new Integer(CDMA2000));
+            } else {
+                // The number will be dialed over UTRAN or GERAN.
+                ratList.remove(new Integer(CDMA2000));
+            }
+        }
+
+        return ratList;
+    }
+
+    private @NonNull List<Integer> getDomainPreference() {
+        int[] domains = mDomainPreference;
+        if (isInRoaming()) domains = mDomainPreferenceRoam;
+
+        List<Integer> domainList = new ArrayList<Integer>();
+        for (int i = 0; i < domains.length; i++) {
+            domainList.add(domains[i]);
+        }
+        return domainList;
+    }
+
+    private boolean isInRoaming() {
+        if (!SubscriptionManager.isValidSubscriptionId(getSubId())) return false;
+
+        TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+        tm = tm.createForSubscriptionId(getSubId());
+        String netIso = tm.getNetworkCountryIso();
+
+        EmergencyRegResult regResult = mSelectionAttributes.getEmergencyRegResult();
+        if (regResult != null) {
+            if (regResult.getRegState() == REGISTRATION_STATE_HOME) return false;
+            if (regResult.getRegState() == REGISTRATION_STATE_ROAMING) return true;
+
+            String iso = regResult.getIso();
+            if (!TextUtils.isEmpty(iso)) netIso = iso;
+        }
+
+        String simIso = tm.getSimCountryIso();
+        logi("isInRoaming simIso=" + simIso + ", netIso=" + netIso);
+
+        if (TextUtils.isEmpty(simIso)) return false;
+        if (TextUtils.isEmpty(netIso)) return false;
+
+        return !(TextUtils.equals(simIso, netIso));
+    }
+
+    /**
+     * Determines whether IMS is registered over Wi-Fi.
+     *
+     * @return {@code true} if IMS is registered over Wi-Fi.
+     */
+    private boolean isImsRegisteredOverWifi() {
+        boolean ret = false;
+        if (SubscriptionManager.isValidSubscriptionId(getSubId())) {
+            ret = mImsStateTracker.isImsRegisteredOverWlan();
+        }
+
+        logi("isImsRegisteredOverWifi " + ret);
+        return ret;
+    }
+
+    /**
+     * Determines whether IMS is registered with voice capability.
+     *
+     * @return {@code true} if IMS is registered with voice capability.
+     */
+    private boolean isImsRegisteredWithVoiceCapability() {
+        boolean ret = mImsRegistered && mIsVoiceCapable;
+
+        logi("isImsRegisteredWithVoiceCapability " + ret);
+        return ret;
+    }
+
+    private void onWlanSelected() {
+        logi("onWlanSelected");
+        mLastTransportType = TRANSPORT_TYPE_WLAN;
+        mVoWifiTrialCount++;
+        mTransportSelectorCallback.onWlanSelected();
+        mWwanSelectorCallback = null;
+    }
+
+    private void onWwanSelected(Runnable runnable) {
+        logi("onWwanSelected");
+        if (mLastTransportType == TRANSPORT_TYPE_WWAN
+                && mWwanSelectorCallback != null) {
+            logi("onWwanSelected already notified");
+            runnable.run();
+            return;
+        }
+
+        mLastTransportType = TRANSPORT_TYPE_WWAN;
+        mTransportSelectorCallback.onWwanSelected((callback) -> {
+            mWwanSelectorCallback = callback;
+            runnable.run();
+        });
+    }
+
+    private void onWwanNetworkTypeSelected(@RadioAccessNetworkType int accessNetworkType) {
+        logi("onWwanNetworkTypeSelected " + accessNetworkTypeToString(accessNetworkType));
+        if (mWwanSelectorCallback == null) {
+            logi("onWwanNetworkTypeSelected callback is null");
+            return;
+        }
+
+        mDomainSelected = true;
+        mLastNetworkType = accessNetworkType;
+        int domain = NetworkRegistrationInfo.DOMAIN_CS;
+        if (accessNetworkType == EUTRAN || accessNetworkType == NGRAN) {
+            domain = NetworkRegistrationInfo.DOMAIN_PS;
+        }
+        mWwanSelectorCallback.onDomainSelected(domain);
+    }
+
+    private static String arrayToString(int[] intArray, IntFunction<String> func) {
+        int length = intArray.length;
+        StringBuilder sb = new StringBuilder("{");
+        if (length > 0) {
+            int i = 0;
+            sb.append(func.apply(intArray[i++]));
+            while (i < length) {
+                sb.append(", ").append(func.apply(intArray[i++]));
+            }
+        }
+        sb.append("}");
+        return sb.toString();
+    }
+
+    private static String arrayToString(String[] stringArray) {
+        StringBuilder sb;
+        int length = stringArray.length;
+        sb = new StringBuilder("{");
+        if (length > 0) {
+            int i = 0;
+            sb.append(stringArray[i++]);
+            while (i < length) {
+                sb.append(", ").append(stringArray[i++]);
+            }
+        }
+        sb.append("}");
+        return sb.toString();
+    }
+
+    private static String domainPreferenceToString(
+            @CarrierConfigManager.ImsEmergency.EmergencyDomain int domain) {
+        switch (domain) {
+            case DOMAIN_CS: return "CS";
+            case DOMAIN_PS_3GPP: return "PS_3GPP";
+            case DOMAIN_PS_NON_3GPP: return "PS_NON_3GPP";
+            default: return "UNKNOWN";
+        }
+    }
+
+    private static String carrierConfigNetworkScanTypeToString(
+            @CarrierConfigManager.ImsEmergency.EmergencyScanType int scanType) {
+        switch (scanType) {
+            case CarrierConfigManager.ImsEmergency.SCAN_TYPE_NO_PREFERENCE: return "NO_PREF";
+            case CarrierConfigManager.ImsEmergency.SCAN_TYPE_FULL_SERVICE: return "FULL";
+            case SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE: return "FULL_N_LIMITED";
+            default: return "UNKNOWN";
+        }
+    }
+
+    private static String accessNetworkTypeToString(
+            @RadioAccessNetworkType int accessNetworkType) {
+        switch (accessNetworkType) {
+            case AccessNetworkType.UNKNOWN: return "UNKNOWN";
+            case AccessNetworkType.GERAN: return "GERAN";
+            case AccessNetworkType.UTRAN: return "UTRAN";
+            case AccessNetworkType.EUTRAN: return "EUTRAN";
+            case AccessNetworkType.CDMA2000: return "CDMA2000";
+            case AccessNetworkType.IWLAN: return "IWLAN";
+            case AccessNetworkType.NGRAN: return "NGRAN";
+            default: return Integer.toString(accessNetworkType);
+        }
+    }
+
+    /**
+     * Destroys the instance.
+     */
+    @VisibleForTesting
+    public void destroy() {
+        if (DBG) logd("destroy");
+
+        releaseWakeLock();
+
+        mDestroyed = true;
+        mImsStateTracker.removeBarringInfoListener(this);
+        mImsStateTracker.removeImsStateListener(this);
+
+        super.destroy();
+    }
+
+    private void acquireWakeLock() {
+        if (mPartialWakeLock != null) {
+            synchronized (mPartialWakeLock) {
+                logi("acquireWakeLock");
+                mPartialWakeLock.acquire();
+            }
+        }
+    }
+
+    private void releaseWakeLock() {
+        if (mPartialWakeLock != null) {
+            synchronized (mPartialWakeLock) {
+                if (mPartialWakeLock.isHeld()) {
+                    logi("releaseWakeLock");
+                    mPartialWakeLock.release();
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void logi(String msg) {
+        super.logi(msg);
+        sLocalLog.log(msg);
+    }
+
+    @Override
+    protected void loge(String msg) {
+        super.loge(msg);
+        sLocalLog.log(msg);
+    }
+}
diff --git a/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java b/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
index 56b9b68..d537281 100644
--- a/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
+++ b/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
@@ -88,9 +88,8 @@
             switch (selectorType) {
                 case SELECTOR_TYPE_CALLING:
                     if (isEmergency) {
-                        // TODO(ag/19990283) uncomment when emergency call domain selector is ready.
-                        /*selector = new EmergencyCallDomainSelector(context, slotId, subId, looper,
-                                imsStateTracker, listener);*/
+                        selector = new EmergencyCallDomainSelector(context, slotId, subId, looper,
+                                imsStateTracker, listener);
                     } else {
                         // TODO(ag/20024470) uncomment when normal call domain selector is ready.
                         /*selector = new NormalCallDomainSelector(context, slotId, subId, looper,
diff --git a/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java b/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
new file mode 100644
index 0000000..e1de0ab
--- /dev/null
+++ b/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
@@ -0,0 +1,1081 @@
+/*
+ * 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.AccessNetworkConstants.AccessNetworkType.EUTRAN;
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.GERAN;
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.UNKNOWN;
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.UTRAN;
+import static android.telephony.BarringInfo.BARRING_SERVICE_TYPE_EMERGENCY;
+import static android.telephony.BarringInfo.BarringServiceInfo.BARRING_TYPE_UNCONDITIONAL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_SCAN_TIMER_SEC_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.SCAN_TYPE_NO_PREFERENCE;
+import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN;
+
+import static com.android.services.telephony.domainselection.EmergencyCallDomainSelector.MSG_NETWORK_SCAN_TIMEOUT;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.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.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.os.PowerManager;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.BarringInfo;
+import android.telephony.CarrierConfigManager;
+import android.telephony.CellIdentityLte;
+import android.telephony.DomainSelectionService;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.EmergencyRegResult;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.TelephonyManager;
+import android.telephony.TransportSelectorCallback;
+import android.telephony.WwanSelectorCallback;
+import android.telephony.ims.ImsManager;
+import android.telephony.ims.ImsMmTelManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableLooper;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.TestContext;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Unit tests for EmergencyCallDomainSelector
+ */
+public class EmergencyCallDomainSelectorTest {
+    private static final String TAG = "EmergencyCallDomainSelectorTest";
+
+    private static final int SLOT_0 = 0;
+    private static final int SLOT_0_SUB_ID = 1;
+
+    @Mock private CarrierConfigManager mCarrierConfigManager;
+    @Mock private TelephonyManager mTelephonyManager;
+    @Mock private WwanSelectorCallback mWwanSelectorCallback;
+    @Mock private TransportSelectorCallback mTransportSelectorCallback;
+    @Mock private ImsMmTelManager mMmTelManager;
+    @Mock private ImsStateTracker mImsStateTracker;
+    @Mock private DomainSelectorBase.DestroyListener mDestroyListener;
+
+    private Context mContext;
+
+    private HandlerThread mHandlerThread;
+    private TestableLooper mLooper;
+    private EmergencyCallDomainSelector mDomainSelector;
+    private SelectionAttributes mSelectionAttributes;
+    private @AccessNetworkConstants.RadioAccessNetworkType List<Integer> mAccessNetwork;
+    private PowerManager mPowerManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = new TestContext() {
+            @Override
+            public String getSystemServiceName(Class<?> serviceClass) {
+                if (serviceClass == ImsManager.class) {
+                    return Context.TELEPHONY_IMS_SERVICE;
+                } else if (serviceClass == TelephonyManager.class) {
+                    return Context.TELEPHONY_SERVICE;
+                } else if (serviceClass == CarrierConfigManager.class) {
+                    return Context.CARRIER_CONFIG_SERVICE;
+                } else if (serviceClass == PowerManager.class) {
+                    return Context.POWER_SERVICE;
+                }
+                return super.getSystemServiceName(serviceClass);
+            }
+
+            @Override
+            public Object getSystemService(String name) {
+                switch (name) {
+                    case (Context.POWER_SERVICE) : {
+                        return mPowerManager;
+                    }
+                }
+                return super.getSystemService(name);
+            }
+
+            @Override
+            public String getOpPackageName() {
+                return "";
+            }
+        };
+
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        mHandlerThread = new HandlerThread("EmergencyCallDomainSelectorTest");
+        mHandlerThread.start();
+
+        try {
+            mLooper = new TestableLooper(mHandlerThread.getLooper());
+        } catch (Exception e) {
+            logd("Unable to create looper from handler.");
+        }
+
+        mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+        when(mTelephonyManager.createForSubscriptionId(anyInt()))
+                .thenReturn(mTelephonyManager);
+        when(mTelephonyManager.getNetworkCountryIso()).thenReturn("");
+
+        mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt()))
+            .thenReturn(getDefaultPersistableBundle());
+
+        IPowerManager powerManager = mock(IPowerManager.class);
+        mPowerManager = new PowerManager(mContext, powerManager, mock(IThermalService.class),
+                new Handler(mHandlerThread.getLooper()));
+
+        ImsManager imsManager = mContext.getSystemService(ImsManager.class);
+        when(imsManager.getImsMmTelManager(anyInt())).thenReturn(mMmTelManager);
+        when(mMmTelManager.isAdvancedCallingSettingEnabled()).thenReturn(true);
+
+        when(mTransportSelectorCallback.onWwanSelected()).thenReturn(mWwanSelectorCallback);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Consumer<WwanSelectorCallback> consumer =
+                        (Consumer<WwanSelectorCallback>) invocation.getArguments()[0];
+                consumer.accept(mWwanSelectorCallback);
+                return null;
+            }
+        }).when(mTransportSelectorCallback).onWwanSelected(any());
+
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                mAccessNetwork = (List<Integer>) invocation.getArguments()[0];
+                return null;
+            }
+        }).when(mWwanSelectorCallback).onRequestEmergencyNetworkScan(
+                any(), anyInt(), any(), any());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mDomainSelector != null) {
+            mDomainSelector.destroy();
+            mDomainSelector = null;
+        }
+
+        if (mLooper != null) {
+            mLooper.destroy();
+            mLooper = null;
+        }
+    }
+
+    @SmallTest
+    @Test
+    public void testInit() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+
+        verify(mWwanSelectorCallback, times(0)).onRequestEmergencyNetworkScan(
+                any(), anyInt(), any(), any());
+        verify(mWwanSelectorCallback, times(0)).onDomainSelected(anyInt());
+    }
+
+    @Test
+    public void testDefaultCombinedImsRegisteredBarredSelectCs() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(true);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+                true, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsService();
+
+        verifyCsDialed();
+    }
+
+    @Test
+    public void testDefaultCombinedImsRegisteredSelectPs() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+                true, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsService();
+
+        verifyPsDialed();
+    }
+
+    @Test
+    public void testDefaultCombinedImsNotRegisteredSelectCs() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+                true, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        verifyCsDialed();
+    }
+
+    @Test
+    public void testDefaultCombinedImsNotRegisteredBarredSelectCs() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(true);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+                true, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        verifyCsDialed();
+    }
+
+    @Test
+    public void testDefaultCombinedImsRegisteredEmsOffBarredSelectCs() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(true);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+                true, false, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsService();
+
+        verifyCsDialed();
+    }
+
+    @Test
+    public void testDefaultCombinedImsRegisteredEmsOffSelectCs() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+                true, false, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsService();
+
+        verifyCsDialed();
+    }
+
+    @Test
+    public void testDefaultCombinedImsNotRegisteredEmsOffSelectCs() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+                true, false, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        verifyCsDialed();
+    }
+
+    @Test
+    public void testDefaultCombinedImsNotRegisteredEmsOffBarredSelectCs() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(true);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+                true, false, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        verifyCsDialed();
+    }
+
+    @Test
+    public void testDefaultCombinedImsRegisteredVopsOffBarredSelectCs() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(true);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+                false, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsService();
+
+        verifyCsDialed();
+    }
+
+    @Test
+    public void testDefaultCombinedImsRegisteredVopsOffSelectCs() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+                false, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsService();
+
+        verifyCsDialed();
+    }
+
+    @Test
+    public void testDefaultCombinedImsNotRegisteredVopsOffSelectCs() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+                false, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        verifyCsDialed();
+    }
+
+    @Test
+    public void testDefaultCombinedImsNotRegisteredVopsOffBarredSelectCs() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(true);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+                false, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        verifyCsDialed();
+    }
+
+    @Test
+    public void testDefaultCombinedImsRegisteredVopsOffEmsOffBarredSelectCs() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(true);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+                false, false, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsService();
+
+        verifyCsDialed();
+    }
+
+    @Test
+    public void testDefaultCombinedImsRegisteredVopsOffEmsOffSelectCs() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+                false, false, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsService();
+
+        verifyCsDialed();
+    }
+
+    @Test
+    public void testDefaultCombinedImsNotRegisteredVopsOffEmsOffSelectCs() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+                false, false, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        verifyCsDialed();
+    }
+
+    @Test
+    public void testDefaultCombinedImsNotRegisteredVopsOffEmsOffBarredSelectCs() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(true);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+                false, false, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        verifyCsDialed();
+    }
+
+    @Test
+    public void testDefaultCsSelectCs() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(UTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_CS,
+                true, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        verifyCsDialed();
+    }
+
+    @Test
+    public void testDefaultEpsImsRegisteredBarredScanPsPreferred() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(true);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                true, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsService();
+
+        verifyScanPsPreferred();
+    }
+
+    @Test
+    public void testDefaultEpsImsRegisteredSelectPs() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                true, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsService();
+
+        verifyPsDialed();
+    }
+
+    @Test
+    public void testDefaultEpsImsNotRegisteredSelectPs() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                true, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        verifyPsDialed();
+    }
+
+    @Test
+    public void testDefaultEpsImsNotRegisteredBarredSelectScanPsPreferred() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(true);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                true, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        verifyScanPsPreferred();
+    }
+
+    @Test
+    public void testDefaultEpsImsRegisteredEmsOffBarredScanPsPreferred() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(true);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                true, false, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsService();
+
+        verifyScanPsPreferred();
+    }
+
+    @Test
+    public void testDefaultEpsImsRegisteredEmsOffScanPsPreferred() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                true, false, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsService();
+
+        verifyScanPsPreferred();
+    }
+
+    @Test
+    public void testDefaultEpsImsNotRegisteredEmsOffScanPsPreferred() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                true, false, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        verifyScanPsPreferred();
+    }
+
+    @Test
+    public void testDefaultEpsImsNotRegisteredEmsOffBarredScanPsPreferred() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(true);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                true, false, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        verifyScanPsPreferred();
+    }
+
+    @Test
+    public void testDefaultEpsImsRegisteredVopsOffBarredScanPsPreferred() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(true);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                false, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsService();
+
+        verifyScanPsPreferred();
+    }
+
+    @Test
+    public void testDefaultEpsImsRegisteredVopsOffScanPsPreferred() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                false, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsService();
+
+        verifyScanPsPreferred();
+    }
+
+    @Test
+    public void testDefaultEpsImsNotRegisteredVopsOffScanPsPreferred() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                false, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        verifyScanPsPreferred();
+    }
+
+    @Test
+    public void testDefaultEpsImsNotRegisteredVopsOffBarredScanPsPreferred() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(true);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                false, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        verifyScanPsPreferred();
+    }
+
+    @Test
+    public void testDefaultEpsImsRegisteredVopsOffEmsOffBarredScanPsPreferred() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(true);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                false, false, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsService();
+
+        verifyScanPsPreferred();
+    }
+
+    @Test
+    public void testDefaultEpsImsRegisteredVopsOffEmsOffScanPsPreferred() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                false, false, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsService();
+
+        verifyScanPsPreferred();
+    }
+
+    @Test
+    public void testDefaultEpsImsNotRegisteredVopsOffEmsOffScanPsPreferred() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                false, false, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        verifyScanPsPreferred();
+    }
+
+    @Test
+    public void testDefaultEpsNotRegisteredVopsOffEmsOffBarredScanPsPreferred() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(true);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                false, false, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        verifyScanPsPreferred();
+    }
+
+    @Test
+    public void testDefaultOutOfServiceScanPsPreferred() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(
+                UNKNOWN, REGISTRATION_STATE_UNKNOWN, 0, false, false, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        verifyScanPsPreferred();
+    }
+
+    @Test
+    public void testVoLteOnEpsImsNotRegisteredSelectPs() throws Exception {
+        PersistableBundle bundle = getDefaultPersistableBundle();
+        bundle.putBoolean(KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL, true);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                true, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        // Requires VoLTE enabled and VoLTE is enabled.
+        verifyPsDialed();
+    }
+
+    @Test
+    public void testVoLteOffEpsImsNotRegisteredSelectCs() throws Exception {
+        PersistableBundle bundle = getDefaultPersistableBundle();
+        bundle.putBoolean(KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL, true);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+
+        // Disable VoLTE.
+        when(mMmTelManager.isAdvancedCallingSettingEnabled()).thenReturn(false);
+
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                true, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        // Requires VoLTE enabled but VoLTE is'nt enabled.
+        verifyCsDialed();
+    }
+
+    @Test
+    public void testRequiresRegEpsImsNotRegisteredScanCsPreferred() throws Exception {
+        PersistableBundle bundle = getDefaultPersistableBundle();
+        bundle.putBoolean(KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL, true);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(false);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                true, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsServiceUnregistered();
+
+        verifyScanCsPreferred();
+    }
+
+    @Test
+    public void testDefaultEpsImsRegisteredBarredScanTimeoutWifi() throws Exception {
+        createSelector(SLOT_0_SUB_ID);
+        unsolBarringInfoChanged(true);
+
+        EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                true, true, 0, 0, "", "");
+        SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+        mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+        processAllMessages();
+
+        bindImsService(true);
+
+        verifyScanPsPreferred();
+
+        assertTrue(mDomainSelector.hasMessages(MSG_NETWORK_SCAN_TIMEOUT));
+
+        mDomainSelector.handleMessage(mDomainSelector.obtainMessage(MSG_NETWORK_SCAN_TIMEOUT));
+
+        verify(mTransportSelectorCallback, times(1)).onWlanSelected();
+    }
+
+    private void createSelector(int subId) throws Exception {
+        mDomainSelector = new EmergencyCallDomainSelector(
+                mContext, SLOT_0, subId, mHandlerThread.getLooper(),
+                mImsStateTracker, mDestroyListener);
+
+        replaceInstance(DomainSelectorBase.class,
+                "mWwanSelectorCallback", mDomainSelector, mWwanSelectorCallback);
+    }
+
+    private void verifyCsDialed() {
+        verify(mWwanSelectorCallback, times(1)).onDomainSelected(eq(DOMAIN_CS));
+    }
+
+    private void verifyPsDialed() {
+        verify(mWwanSelectorCallback, times(1)).onDomainSelected(eq(DOMAIN_PS));
+    }
+
+    private void verifyScanPsPreferred() {
+        verifyScanPreferred(DomainSelectionService.SCAN_TYPE_NO_PREFERENCE, EUTRAN);
+    }
+
+    private void verifyScanCsPreferred() {
+        verifyScanPreferred(DomainSelectionService.SCAN_TYPE_NO_PREFERENCE, UTRAN);
+    }
+
+    private void verifyScanPreferred(int scanType, int expectedPreferredAccessNetwork) {
+        verify(mWwanSelectorCallback, times(1)).onRequestEmergencyNetworkScan(
+                any(), eq(scanType), any(), any());
+        assertEquals(expectedPreferredAccessNetwork, (int) mAccessNetwork.get(0));
+    }
+
+    private void unsolBarringInfoChanged(boolean barred) {
+        SparseArray<BarringInfo.BarringServiceInfo> serviceInfos = new SparseArray<>();
+        if (barred) {
+            serviceInfos.put(BARRING_SERVICE_TYPE_EMERGENCY,
+                    new BarringInfo.BarringServiceInfo(BARRING_TYPE_UNCONDITIONAL, false, 0, 0));
+        }
+        mDomainSelector.onBarringInfoUpdated(new BarringInfo(new CellIdentityLte(), serviceInfos));
+    }
+
+    private void bindImsService() {
+        bindImsService(false);
+    }
+
+    private void bindImsService(boolean isWifi) {
+        doReturn(isWifi).when(mImsStateTracker).isImsRegisteredOverWlan();
+        doReturn(true).when(mImsStateTracker).isImsRegistered();
+        mDomainSelector.onImsRegistrationStateChanged();
+        doReturn(true).when(mImsStateTracker).isImsVoiceCapable();
+        mDomainSelector.onImsMmTelCapabilitiesChanged();
+    }
+
+    private void bindImsServiceUnregistered() {
+        doReturn(false).when(mImsStateTracker).isImsRegistered();
+        mDomainSelector.onImsRegistrationStateChanged();
+        doReturn(false).when(mImsStateTracker).isImsVoiceCapable();
+        mDomainSelector.onImsMmTelCapabilitiesChanged();
+    }
+
+    private static EmergencyRegResult getEmergencyRegResult(
+            @AccessNetworkConstants.RadioAccessNetworkType int accessNetwork,
+            @NetworkRegistrationInfo.RegistrationState int regState,
+            @NetworkRegistrationInfo.Domain int domain,
+            boolean isVopsSupported, boolean isEmcBearerSupported, int emc, int emf,
+            @NonNull String mcc, @NonNull String mnc) {
+        return new EmergencyRegResult(accessNetwork, regState,
+                domain, isVopsSupported, isEmcBearerSupported,
+                emc, emf, mcc, mnc, "");
+    }
+
+    private static PersistableBundle getDefaultPersistableBundle() {
+        int[] imsRats = new int[] { EUTRAN };
+        int[] csRats = new int[] { UTRAN, GERAN };
+        int[] imsRoamRats = new int[] { EUTRAN };
+        int[] csRoamRats = new int[] { UTRAN, GERAN };
+        int[] domainPreference = new int[] {
+                CarrierConfigManager.ImsEmergency.DOMAIN_PS_3GPP,
+                CarrierConfigManager.ImsEmergency.DOMAIN_CS,
+                CarrierConfigManager.ImsEmergency.DOMAIN_PS_NON_3GPP
+                };
+        int[] roamDomainPreference = new int[] {
+                CarrierConfigManager.ImsEmergency.DOMAIN_PS_3GPP,
+                CarrierConfigManager.ImsEmergency.DOMAIN_CS,
+                CarrierConfigManager.ImsEmergency.DOMAIN_PS_NON_3GPP
+                };
+        boolean imsWhenVoiceOnCs = false;
+        int maxRetriesOverWiFi = 1;
+        int cellularScanTimerSec = 10;
+        int scanType = SCAN_TYPE_NO_PREFERENCE;
+        boolean requiresImsRegistration = false;
+        boolean requiresVoLteEnabled = false;
+        boolean ltePreferredAfterNrFailed = false;
+        String[] cdmaPreferredNumbers = new String[] {};
+
+        return getPersistableBundle(imsRats, csRats, imsRoamRats, csRoamRats,
+                domainPreference, roamDomainPreference, imsWhenVoiceOnCs, maxRetriesOverWiFi,
+                cellularScanTimerSec, scanType, requiresImsRegistration, requiresVoLteEnabled,
+                ltePreferredAfterNrFailed, cdmaPreferredNumbers);
+    }
+
+    private static PersistableBundle getPersistableBundle(
+            @Nullable int[] imsRats, @Nullable int[] csRats,
+            @Nullable int[] imsRoamRats, @Nullable int[] csRoamRats,
+            @Nullable int[] domainPreference, @Nullable int[] roamDomainPreference,
+            boolean imsWhenVoiceOnCs, int maxRetriesOverWiFi,
+            int cellularScanTimerSec, int scanType, boolean requiresImsRegistration,
+            boolean requiresVoLteEnabled, boolean ltePreferredAfterNrFailed,
+            @Nullable String[] cdmaPreferredNumbers) {
+
+        PersistableBundle bundle  = new PersistableBundle();
+        if (imsRats != null) {
+            bundle.putIntArray(KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY,
+                    imsRats);
+        }
+        if (imsRoamRats != null) {
+            bundle.putIntArray(
+                    KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY,
+                    imsRoamRats);
+        }
+        if (csRats != null) {
+            bundle.putIntArray(KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY,
+                    csRats);
+        }
+        if (csRoamRats != null) {
+            bundle.putIntArray(
+                    KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY,
+                    csRoamRats);
+        }
+        if (domainPreference != null) {
+            bundle.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY, domainPreference);
+        }
+        if (roamDomainPreference != null) {
+            bundle.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY,
+                    roamDomainPreference);
+        }
+        bundle.putBoolean(KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL, imsWhenVoiceOnCs);
+        bundle.putInt(KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT, maxRetriesOverWiFi);
+        bundle.putInt(KEY_EMERGENCY_SCAN_TIMER_SEC_INT, cellularScanTimerSec);
+        bundle.putInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT, scanType);
+        bundle.putBoolean(KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL, requiresImsRegistration);
+        bundle.putBoolean(KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL, requiresVoLteEnabled);
+        bundle.putInt(KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT, 0);
+        bundle.putBoolean(KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL,
+                ltePreferredAfterNrFailed);
+        bundle.putStringArray(KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY,
+                cdmaPreferredNumbers);
+
+        return bundle;
+    }
+
+    public static SelectionAttributes getSelectionAttributes(int slotId, int subId,
+            EmergencyRegResult regResult) {
+        SelectionAttributes.Builder builder =
+                new SelectionAttributes.Builder(slotId, subId, SELECTOR_TYPE_CALLING)
+                .setEmergency(true)
+                .setEmergencyRegResult(regResult);
+        return builder.build();
+    }
+
+    private static void replaceInstance(final Class c,
+            final String instanceName, final Object obj, final Object newValue) throws Exception {
+        Field field = c.getDeclaredField(instanceName);
+        field.setAccessible(true);
+        field.set(obj, newValue);
+    }
+
+    private void processAllMessages() {
+        while (!mLooper.getLooper().getQueue().isIdle()) {
+            mLooper.processAllMessages();
+        }
+    }
+
+    private static void logd(String str) {
+        Log.d(TAG, str);
+    }
+}