Merge "Add text to RTT setting when roaming"
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index 8b74af4..4025bbb 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -20,7 +20,6 @@
 import static android.service.carrier.CarrierService.ICarrierServiceWrapper.RESULT_ERROR;
 
 import android.annotation.NonNull;
-import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -40,7 +39,6 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
-import android.os.UserHandle;
 import android.preference.PreferenceManager;
 import android.service.carrier.CarrierIdentifier;
 import android.service.carrier.CarrierService;
@@ -514,7 +512,7 @@
         pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         pkgFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
         pkgFilter.addDataScheme("package");
-        context.registerReceiverAsUser(mPackageReceiver, UserHandle.ALL, pkgFilter, null, null);
+        context.registerReceiver(mPackageReceiver, pkgFilter);
 
         int numPhones = TelephonyManager.from(context).getPhoneCount();
         mConfigFromDefaultApp = new PersistableBundle[numPhones];
@@ -592,7 +590,7 @@
         }
         intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, phoneId);
         log("Broadcast CARRIER_CONFIG_CHANGED for phone " + phoneId);
-        ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
+        mContext.sendBroadcast(intent);
         mHasSentConfigChange[phoneId] = true;
     }
 
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index fed41b0..04850f3 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -527,6 +527,9 @@
                 maybeTurnCellOn(context, isAirplaneNewlyOn);
                 break;
         }
+        for (Phone phone : PhoneFactory.getPhones()) {
+            phone.getServiceStateTracker().onAirplaneModeChanged(isAirplaneNewlyOn);
+        }
     }
 
     /*
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 6c60f1f..5c6dd83 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -1410,7 +1410,7 @@
         // from the context of the phone app.
         enforceCallPermission();
 
-        if (mAppOps.noteOp(AppOpsManager.OP_CALL_PHONE, Binder.getCallingUid(), callingPackage)
+        if (mAppOps.noteOp(AppOpsManager.OPSTR_CALL_PHONE, Binder.getCallingUid(), callingPackage)
                 != AppOpsManager.MODE_ALLOWED) {
             return;
         }
@@ -2024,14 +2024,18 @@
     /**
      * Returns the target SDK version number for a given package name.
      *
+     * This call MUST be invoked before clearing the calling UID.
+     *
      * @return target SDK if the package is found or INT_MAX.
      */
     private int getTargetSdk(String packageName) {
         try {
-            final ApplicationInfo ai = mApp.getPackageManager().getApplicationInfo(
-                            packageName, 0);
+            final ApplicationInfo ai = mApp.getPackageManager().getApplicationInfoAsUser(
+                    packageName, 0, UserHandle.getUserId(Binder.getCallingUid()));
             if (ai != null) return ai.targetSdkVersion;
         } catch (PackageManager.NameNotFoundException unexpected) {
+            loge("Failed to get package info for pkg="
+                    + packageName + ", uid=" + Binder.getCallingUid());
         }
         return Integer.MAX_VALUE;
     }
@@ -2045,7 +2049,7 @@
                     "getNeighboringCellInfo() is unavailable to callers targeting Q+ SDK levels.");
         }
 
-        if (mAppOps.noteOp(AppOpsManager.OP_NEIGHBORING_CELLS, Binder.getCallingUid(),
+        if (mAppOps.noteOp(AppOpsManager.OPSTR_NEIGHBORING_CELLS, Binder.getCallingUid(),
                 callingPackage) != AppOpsManager.MODE_ALLOWED) {
             return null;
         }
diff --git a/src/com/android/phone/settings/AccessibilitySettingsFragment.java b/src/com/android/phone/settings/AccessibilitySettingsFragment.java
index 40dfc72..3069091 100644
--- a/src/com/android/phone/settings/AccessibilitySettingsFragment.java
+++ b/src/com/android/phone/settings/AccessibilitySettingsFragment.java
@@ -34,6 +34,7 @@
 import com.android.ims.ImsManager;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.SubscriptionController;
 import com.android.phone.PhoneGlobals;
 import com.android.phone.R;
 
@@ -110,8 +111,7 @@
             mButtonHac = null;
         }
 
-        if (PhoneGlobals.getInstance().phoneMgr
-                .isRttSupported(SubscriptionManager.getDefaultVoiceSubscriptionId())) {
+        if (shouldShowRttSetting()) {
             // TODO: this is going to be a on/off switch for now. Ask UX about how to integrate
             // this settings with TTY
             if (TelephonyManager.getDefault().isNetworkRoaming(
@@ -198,6 +198,21 @@
         return false;
     }
 
+    private boolean shouldShowRttSetting() {
+        int subscriptionId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+        if (subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                || subscriptionId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+            for (int subId : SubscriptionController.getInstance().getActiveSubIdList(true)) {
+                if (PhoneGlobals.getInstance().phoneMgr.isRttSupported(subId)) {
+                    return true;
+                }
+            }
+            return false;
+        } else {
+            return PhoneGlobals.getInstance().phoneMgr.isRttSupported(subscriptionId);
+        }
+    }
+
     /**
      * Determines if the device supports TTY per carrier config.
      * @return {@code true} if the carrier supports TTY, {@code false} otherwise.
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 1f157e3..8ef0464 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -259,6 +259,8 @@
     private boolean mCouldManageConference;
     private FeatureFlagProxy mFeatureFlagProxy;
     private boolean mIsEmulatingSinglePartyCall = false;
+    private boolean mIsUsingSimCallManager = false;
+
     /**
      * Where {@link #mIsEmulatingSinglePartyCall} is {@code true}, contains the
      * {@link ConferenceParticipantConnection#getUserEntity()} and
@@ -616,6 +618,7 @@
     private void updateManageConference() {
         boolean couldManageConference = can(Connection.CAPABILITY_MANAGE_CONFERENCE);
         boolean canManageConference = mFeatureFlagProxy.isUsingSinglePartyCallEmulation()
+                && mIsEmulatingSinglePartyCall
                 ? mConferenceParticipantConnections.size() > 1
                 : mConferenceParticipantConnections.size() != 0;
         Log.v(this, "updateManageConference was :%s is:%s", couldManageConference ? "Y" : "N",
@@ -675,6 +678,9 @@
 
             mConferenceHostAddress = new Uri[hostAddresses.size()];
             mConferenceHostAddress = hostAddresses.toArray(mConferenceHostAddress);
+
+            mIsUsingSimCallManager = mTelecomAccountRegistry.isUsingSimCallManager(
+                    mConferenceHostPhoneAccountHandle);
         }
 
         mConferenceHost.addConnectionListener(mConferenceHostListener);
@@ -829,8 +835,18 @@
      * 1. Tell telecom we're a conference again.
      * 2. Restore {@link Connection#CAPABILITY_MANAGE_CONFERENCE} capability.
      * 3. Null out the name/address.
+     *
+     * Note: Single party call emulation is disabled if the conference is taking place via a
+     * sim call manager.  Emulating a single party call requires properties of the conference to be
+     * changed (connect time, address, conference state) which cannot be guaranteed to be relayed
+     * correctly by the sim call manager to Telecom.
      */
     private void stopEmulatingSinglePartyCall() {
+        if (mIsUsingSimCallManager) {
+            Log.i(this, "stopEmulatingSinglePartyCall: using sim call manager; skip.");
+            return;
+        }
+
         Log.i(this, "stopEmulatingSinglePartyCall: conference now has more than one"
                 + " participant; make it look conference-like again.");
         mIsEmulatingSinglePartyCall = false;
@@ -867,8 +883,18 @@
      * 2. Tell telecom we're not a conference.
      * 3. Remove {@link Connection#CAPABILITY_MANAGE_CONFERENCE} capability.
      * 4. Set the name/address to that of the single participant.
+     *
+     * Note: Single party call emulation is disabled if the conference is taking place via a
+     * sim call manager.  Emulating a single party call requires properties of the conference to be
+     * changed (connect time, address, conference state) which cannot be guaranteed to be relayed
+     * correctly by the sim call manager to Telecom.
      */
     private void startEmulatingSinglePartyCall() {
+        if (mIsUsingSimCallManager) {
+            Log.i(this, "startEmulatingSinglePartyCall: using sim call manager; skip.");
+            return;
+        }
+
         Log.i(this, "startEmulatingSinglePartyCall: conference has a single "
                 + "participant; downgrade to single party call.");
 
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index b2fb3ad..3505fe7 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -98,6 +98,7 @@
         private boolean mIsVideoConferencingSupported;
         private boolean mIsMergeOfWifiCallsAllowedWhenVoWifiOff;
         private boolean mIsManageImsConferenceCallSupported;
+        private boolean mIsUsingSimCallManager;
         private boolean mIsShowPreciseFailedCause;
 
         AccountEntry(Phone phone, boolean isEmergency, boolean isDummy) {
@@ -273,7 +274,7 @@
                 capabilities |= PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS;
             }
 
-            mIsEmergencyPreferred = isEmergencyPreferredAccount(subId);
+            mIsEmergencyPreferred = isEmergencyPreferredAccount(subId, mActiveDataSubscriptionId);
             if (mIsEmergencyPreferred) {
                 capabilities |= PhoneAccount.CAPABILITY_EMERGENCY_PREFERRED;
             }
@@ -350,6 +351,7 @@
             mIsMergeOfWifiCallsAllowedWhenVoWifiOff =
                     isCarrierMergeOfWifiCallsAllowedWhenVoWifiOff();
             mIsManageImsConferenceCallSupported = isCarrierManageImsConferenceCallSupported();
+            mIsUsingSimCallManager = isCarrierUsingSimCallManager();
             mIsShowPreciseFailedCause = isCarrierShowPreciseFailedCause();
 
             if (isEmergency && mContext.getResources().getBoolean(
@@ -426,24 +428,47 @@
          * @return true if Telecom should prefer this PhoneAccount, false if there is no preference
          * needed.
          */
-        private boolean isEmergencyPreferredAccount(int subId) {
+        private boolean isEmergencyPreferredAccount(int subId, int activeDataSubId) {
+            Log.d(this, "isEmergencyPreferredAccount: subId=" + subId + ", activeData="
+                    + activeDataSubId);
             final boolean gnssSuplRequiresDefaultData = mContext.getResources().getBoolean(
                     R.bool.config_gnss_supl_requires_default_data_for_emergency);
             if (!gnssSuplRequiresDefaultData) {
+                Log.d(this, "isEmergencyPreferredAccount: Device does not require preference.");
                 // No preference is necessary.
                 return false;
             }
 
-            // Only set a preference on MSIM devices
-            if (mTelephonyManager.getPhoneCount() <= 1) {
+            SubscriptionController controller = SubscriptionController.getInstance();
+            if (controller == null) {
+                Log.d(this, "isEmergencyPreferredAccount: SubscriptionController not available.");
+                return false;
+            }
+            // Only set an emergency preference on devices with multiple active subscriptions
+            // (include opportunistic subscriptions) in this check.
+            // API says never null, but this can return null in testing.
+            int[] activeSubIds = controller.getActiveSubIdList(false);
+            if (activeSubIds == null || activeSubIds.length <= 1) {
+                Log.d(this, "isEmergencyPreferredAccount: one or less active subscriptions.");
                 return false;
             }
             // Check to see if this PhoneAccount is associated with the default Data subscription.
             if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+                Log.d(this, "isEmergencyPreferredAccount: provided subId " + subId + "is not "
+                        + "valid.");
                 return false;
             }
-            SubscriptionController controller = SubscriptionController.getInstance();
-            return controller != null && (controller.getDefaultDataSubId() == subId);
+            int userDefaultData = controller.getDefaultDataSubId();
+            boolean isActiveDataValid = SubscriptionManager.isValidSubscriptionId(activeDataSubId);
+            boolean isActiveDataOpportunistic = isActiveDataValid
+                    && controller.isOpportunistic(activeDataSubId);
+            // compare the activeDataSubId to the subId specified only if it is valid and not an
+            // opportunistic subscription (only supports data). If not, use the current default
+            // defined by the user.
+            Log.d(this, "isEmergencyPreferredAccount: userDefaultData=" + userDefaultData
+                    + ", isActiveDataOppurtunistic=" + isActiveDataOpportunistic);
+            return subId == ((isActiveDataValid && !isActiveDataOpportunistic) ? activeDataSubId :
+                    userDefaultData);
         }
 
         /**
@@ -558,6 +583,19 @@
         }
 
         /**
+         * Determines from carrier config whether the carrier uses a sim call manager.
+         *
+         * @return {@code true} if the carrier uses a sim call manager,
+         *         {@code false} otherwise.
+         */
+        private boolean isCarrierUsingSimCallManager() {
+            PersistableBundle b =
+                    PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
+            return !TextUtils.isEmpty(
+                    b.getString(CarrierConfigManager.KEY_DEFAULT_SIM_CALL_MANAGER_STRING));
+        }
+
+        /**
          * Determines from carrier config whether showing percise call diconnect cause to user
          * is supported.
          *
@@ -633,8 +671,9 @@
             }
         }
 
-        public void updateDefaultDataSubId() {
-            boolean isEmergencyPreferred = isEmergencyPreferredAccount(mPhone.getSubId());
+        public void updateDefaultDataSubId(int activeDataSubId) {
+            boolean isEmergencyPreferred = isEmergencyPreferredAccount(mPhone.getSubId(),
+                    activeDataSubId);
             if (isEmergencyPreferred != mIsEmergencyPreferred) {
                 Log.i(this, "updateDefaultDataSubId - changed, new value: " + isEmergencyPreferred);
                 mAccount = registerPstnPhoneAccount(mIsEmergency, mIsDummy);
@@ -707,6 +746,15 @@
         }
 
         /**
+         * Indicates whether this account uses a sim call manger.
+         * @return {@code true} if the account uses a sim call manager,
+         *         {@code false} otherwise.
+         */
+        public boolean isUsingSimCallManager() {
+            return mIsUsingSimCallManager;
+        }
+
+        /**
          * Indicates whether this account supports showing the precise call disconnect cause
          * to user (i.e. conferencing).
          * @return {@code true} if the account supports showing the precise call disconnect cause,
@@ -790,9 +838,10 @@
 
         @Override
         public void onActiveDataSubscriptionIdChanged(int subId) {
+            mActiveDataSubscriptionId = subId;
             synchronized (mAccountsLock) {
                 for (AccountEntry account : mAccounts) {
-                    account.updateDefaultDataSubId();
+                    account.updateDefaultDataSubId(mActiveDataSubscriptionId);
                 }
             }
         }
@@ -806,6 +855,7 @@
     private List<AccountEntry> mAccounts = new LinkedList<AccountEntry>();
     private final Object mAccountsLock = new Object();
     private int mServiceState = ServiceState.STATE_POWER_OFF;
+    private int mActiveDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private boolean mIsPrimaryUser = true;
 
     // TODO: Remove back-pointer from app singleton to Service, since this is not a preferred
@@ -977,7 +1027,7 @@
      * @param handle The phone account handle to find the subscription address for.
      * @return The address.
      */
-    Uri getAddress(PhoneAccountHandle handle) {
+    public Uri getAddress(PhoneAccountHandle handle) {
         synchronized (mAccountsLock) {
             for (AccountEntry entry : mAccounts) {
                 if (entry.getPhoneAccountHandle().equals(handle)) {
@@ -989,6 +1039,24 @@
     }
 
     /**
+     * Returns whethere a the subscription associated with a {@link PhoneAccountHandle} is using a
+     * sim call manager.
+     *
+     * @param handle The phone account handle to find the subscription address for.
+     * @return {@code true} if a sim call manager is in use, {@code false} otherwise.
+     */
+    public boolean isUsingSimCallManager(PhoneAccountHandle handle) {
+        synchronized (mAccountsLock) {
+            for (AccountEntry entry : mAccounts) {
+                if (entry.getPhoneAccountHandle().equals(handle)) {
+                    return entry.isUsingSimCallManager();
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
      * Sets up all the phone accounts for SIMs on first boot.
      */
     public void setupOnBoot() {
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 632e9ac..7bb6b7d 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -2203,7 +2203,6 @@
         boolean isIms = phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS;
         boolean isVoWifiEnabled = false;
         if (isIms) {
-            ImsPhone imsPhone = (ImsPhone) phone;
             isVoWifiEnabled = ImsUtil.isWfcEnabled(phone.getContext(), phone.getPhoneId());
         }
         PhoneAccountHandle phoneAccountHandle = isIms ? PhoneUtils
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 4b29f4a..927b2c9 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -16,6 +16,7 @@
 
 package com.android.services.telephony;
 
+import android.annotation.NonNull;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -53,6 +54,7 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.PhoneSwitcher;
 import com.android.internal.telephony.RIL;
 import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
@@ -71,6 +73,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
 
 import javax.annotation.Nullable;
@@ -80,6 +84,10 @@
  */
 public class TelephonyConnectionService extends ConnectionService {
 
+    // Timeout before we continue with the emergency call without waiting for DDS switch response
+    // from the modem.
+    private static final int DEFAULT_DATA_SWITCH_TIMEOUT_MS = 1000;
+
     // If configured, reject attempts to dial numbers matching this pattern.
     private static final Pattern CDMA_ACTIVATION_CODE_REGEX_PATTERN =
             Pattern.compile("\\*228[0-9]{0,2}");
@@ -508,21 +516,41 @@
             final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber,
                     /* Note: when not an emergency, handle can be null for unknown callers */
                     handle == null ? null : handle.getSchemeSpecificPart());
-            Connection resultConnection = getTelephonyConnection(request, numberToDial,
-                    isEmergencyNumber, handle, phone);
-            // If there was a failure, the resulting connection will not be a TelephonyConnection,
-            // so don't place the call!
-            if (resultConnection instanceof TelephonyConnection) {
-                if (request.getExtras() != null && request.getExtras().getBoolean(
-                        TelecomManager.EXTRA_USE_ASSISTED_DIALING, false)) {
-                    ((TelephonyConnection) resultConnection).setIsUsingAssistedDialing(true);
-                }
-                placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request);
+            if (!isEmergencyNumber) {
+                final Connection resultConnection = getTelephonyConnection(request, numberToDial,
+                        false, handle, phone);
+                return placeOutgoingConnection(request, resultConnection, phone);
+            } else {
+                final Connection resultConnection = getTelephonyConnection(request, numberToDial,
+                        true, handle, phone);
+                CompletableFuture<Boolean> phoneFuture = delayDialForDdsSwitch(phone);
+                phoneFuture.whenComplete((result, error) -> {
+                    if (error != null) {
+                        Log.w(this, "onCreateOutgoingConn - delayDialForDdsSwitch exception= "
+                                + error.getMessage());
+                    }
+                    Log.i(this, "onCreateOutgoingConn - delayDialForDdsSwitch result = " + result);
+                    placeOutgoingConnection(request, resultConnection, phone);
+                });
+                return resultConnection;
             }
-            return resultConnection;
         }
     }
 
+    private Connection placeOutgoingConnection(ConnectionRequest request,
+            Connection resultConnection, Phone phone) {
+        // If there was a failure, the resulting connection will not be a TelephonyConnection,
+        // so don't place the call!
+        if (resultConnection instanceof TelephonyConnection) {
+            if (request.getExtras() != null && request.getExtras().getBoolean(
+                    TelecomManager.EXTRA_USE_ASSISTED_DIALING, false)) {
+                ((TelephonyConnection) resultConnection).setIsUsingAssistedDialing(true);
+            }
+            placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request);
+        }
+        return resultConnection;
+    }
+
     private boolean isEmergencyNumberTestNumber(String number) {
         number = PhoneNumberUtils.stripSeparators(number);
         Map<Integer, List<EmergencyNumber>> list =
@@ -567,40 +595,26 @@
                     + "placement.");
             return;
         }
+        // Get the right phone object since the radio has been turned on successfully.
         if (isRadioReady) {
-            // Get the right phone object since the radio has been turned on
-            // successfully.
             final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber,
                     /* Note: when not an emergency, handle can be null for unknown callers */
                     handle == null ? null : handle.getSchemeSpecificPart());
-            // If the PhoneType of the Phone being used is different than the Default Phone, then we
-            // need create a new Connection using that PhoneType and replace it in Telecom.
-            if (phone.getPhoneType() != originalPhoneType) {
-                Connection repConnection = getTelephonyConnection(request, numberToDial,
-                        isEmergencyNumber, handle, phone);
-                // If there was a failure, the resulting connection will not be a
-                // TelephonyConnection, so don't place the call, just return!
-                if (repConnection instanceof TelephonyConnection) {
-                    placeOutgoingConnection((TelephonyConnection) repConnection, phone, request);
-                }
-                // Notify Telecom of the new Connection type.
-                // TODO: Switch out the underlying connection instead of creating a new
-                // one and causing UI Jank.
-                boolean noActiveSimCard = SubscriptionController.getInstance()
-                        .getActiveSubInfoCount(phone.getContext().getOpPackageName()) == 0;
-                // If there's no active sim card and the device is in emergency mode, use E account.
-                addExistingConnection(PhoneUtils.makePstnPhoneAccountHandleWithPrefix(
-                        phone, "", isEmergencyNumber && noActiveSimCard), repConnection);
-                // Remove the old connection from Telecom after.
-                originalConnection.setDisconnected(
-                        DisconnectCauseUtil.toTelecomDisconnectCause(
-                                android.telephony.DisconnectCause.OUTGOING_CANCELED,
-                                "Reconnecting outgoing Emergency Call.",
-                                phone.getPhoneId()));
-                originalConnection.destroy();
+            if (!isEmergencyNumber) {
+                adjustAndPlaceOutgoingConnection(phone, originalConnection, request, numberToDial,
+                        handle, originalPhoneType, false);
             } else {
-                placeOutgoingConnection((TelephonyConnection) originalConnection, phone, request);
+                delayDialForDdsSwitch(phone).whenComplete((result, error) -> {
+                    if (error != null) {
+                        Log.w(this, "handleOnComplete - delayDialForDdsSwitch exception= "
+                                + error.getMessage());
+                    }
+                    Log.i(this, "handleOnComplete - delayDialForDdsSwitch result = " + result);
+                    adjustAndPlaceOutgoingConnection(phone, originalConnection, request,
+                            numberToDial, handle, originalPhoneType, true);
+                });
             }
+
         } else {
             Log.w(this, "onCreateOutgoingConnection, failed to turn on radio");
             originalConnection.setDisconnected(
@@ -611,6 +625,39 @@
         }
     }
 
+    private void adjustAndPlaceOutgoingConnection(Phone phone, Connection connectionToEvaluate,
+            ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType,
+            boolean isEmergencyNumber) {
+        // If the PhoneType of the Phone being used is different than the Default Phone, then we
+        // need to create a new Connection using that PhoneType and replace it in Telecom.
+        if (phone.getPhoneType() != originalPhoneType) {
+            Connection repConnection = getTelephonyConnection(request, numberToDial,
+                    isEmergencyNumber, handle, phone);
+            // If there was a failure, the resulting connection will not be a TelephonyConnection,
+            // so don't place the call, just return!
+            if (repConnection instanceof TelephonyConnection) {
+                placeOutgoingConnection((TelephonyConnection) repConnection, phone, request);
+            }
+            // Notify Telecom of the new Connection type.
+            // TODO: Switch out the underlying connection instead of creating a new
+            // one and causing UI Jank.
+            boolean noActiveSimCard = SubscriptionController.getInstance()
+                    .getActiveSubInfoCount(phone.getContext().getOpPackageName()) == 0;
+            // If there's no active sim card and the device is in emergency mode, use E account.
+            addExistingConnection(PhoneUtils.makePstnPhoneAccountHandleWithPrefix(
+                    phone, "", isEmergencyNumber && noActiveSimCard), repConnection);
+            // Remove the old connection from Telecom after.
+            connectionToEvaluate.setDisconnected(
+                    DisconnectCauseUtil.toTelecomDisconnectCause(
+                            android.telephony.DisconnectCause.OUTGOING_CANCELED,
+                            "Reconnecting outgoing Emergency Call.",
+                            phone.getPhoneId()));
+            connectionToEvaluate.destroy();
+        } else {
+            placeOutgoingConnection((TelephonyConnection) connectionToEvaluate, phone, request);
+        }
+    }
+
     /**
      * @return {@code true} if any other call is disabling the ability to add calls, {@code false}
      *      otherwise.
@@ -1318,8 +1365,7 @@
         // If this is an emergency call and the phone we originally planned to make this call
         // with is not in service or was invalid, try to find one that is in service, using the
         // default as a last chance backup.
-        if (isEmergency && (chosenPhone == null || ServiceState.STATE_IN_SERVICE != chosenPhone
-                .getServiceState().getState())) {
+        if (isEmergency && (chosenPhone == null || !isAvailableForEmergencyCalls(chosenPhone))) {
             Log.d(this, "getPhoneForAccount: phone for phone acct handle %s is out of service "
                     + "or invalid for emergency call.", accountHandle);
             chosenPhone = getPhoneForEmergencyCall(emergencyNumberAddress);
@@ -1329,6 +1375,98 @@
         return chosenPhone;
     }
 
+    private CompletableFuture<Boolean> delayDialForDdsSwitch(Phone phone) {
+        if (phone == null) {
+            return CompletableFuture.completedFuture(Boolean.TRUE);
+        }
+        return possiblyOverrideDefaultDataForEmergencyCall(phone)
+                .completeOnTimeout(false, DEFAULT_DATA_SWITCH_TIMEOUT_MS,
+                        TimeUnit.MILLISECONDS);
+    }
+
+    /**
+     * If needed, block until Default Data subscription is switched for outgoing emergency call.
+     *
+     * In some cases, we need to try to switch the Default Data subscription before placing the
+     * emergency call on DSDS devices. This includes the following situation:
+     * - The modem does not support processing GNSS SUPL requests on the non-default data
+     * subscription. For some carriers that do not provide a control plane fallback mechanism, the
+     * SUPL request will be dropped and we will not be able to get the user's location for the
+     * emergency call. In this case, we need to swap default data temporarily.
+     * @param phone Evaluates whether or not the default data should be moved to the phone
+     *              specified. Should not be null.
+     */
+    private CompletableFuture<Boolean> possiblyOverrideDefaultDataForEmergencyCall(
+            @NonNull Phone phone) {
+        TelephonyManager telephony = TelephonyManager.from(phone.getContext());
+        int phoneCount = telephony.getPhoneCount();
+        // Do not override DDS if this is a single SIM device.
+        if (phoneCount <= PhoneConstants.MAX_PHONE_COUNT_SINGLE_SIM) {
+            return CompletableFuture.completedFuture(Boolean.TRUE);
+        }
+
+        CarrierConfigManager cfgManager = (CarrierConfigManager)
+                phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (cfgManager == null) {
+            // For some reason CarrierConfigManager is unavailable. Do not block emergency call.
+            Log.w(this, "possiblyOverrideDefaultDataForEmergencyCall: couldn't get"
+                    + "CarrierConfigManager");
+            return CompletableFuture.completedFuture(Boolean.TRUE);
+        }
+        // Only override default data if we are IN_SERVICE and on a home network. We don't want to
+        // perform a DDS switch of we are on a roaming network, where SUPL may not be available.
+        boolean isPhoneAvailableForEmergency = isAvailableForEmergencyCalls(phone);
+        boolean isRoaming = phone.getServiceState().getVoiceRoaming();
+        if (!isPhoneAvailableForEmergency || isRoaming) {
+            Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, avail = "
+                    + isPhoneAvailableForEmergency + ", roaming = " + isRoaming);
+            return CompletableFuture.completedFuture(Boolean.TRUE);
+        }
+
+        // Do not switch Default data if this device supports emergency SUPL on non-DDS.
+        final boolean gnssSuplRequiresDefaultData = phone.getContext().getResources().getBoolean(
+                R.bool.config_gnss_supl_requires_default_data_for_emergency);
+        if (!gnssSuplRequiresDefaultData) {
+            Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, does not "
+                    + "require DDS switch.");
+            return CompletableFuture.completedFuture(Boolean.TRUE);
+        }
+
+        final boolean supportsCpFallback = cfgManager.getConfigForSubId(phone.getSubId())
+                .getInt(CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
+                        CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_ONLY)
+                != CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY;
+        if (supportsCpFallback) {
+            Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, carrier "
+                    + "supports CP fallback.");
+            // Do not try to swap default data if we support CS fallback, do not want to introduce
+            // a lag in emergency call setup time if possible.
+            return CompletableFuture.completedFuture(Boolean.TRUE);
+        }
+
+        // Get extension time, may be 0 for some carriers that support ECBM as well. Use
+        // CarrierConfig default if format fails.
+        int extensionTime = 0;
+        try {
+            extensionTime = Integer.valueOf(cfgManager.getConfigForSubId(phone.getSubId())
+                    .getString(CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0"));
+        } catch (NumberFormatException e) {
+            // Just use default.
+        }
+        CompletableFuture<Boolean> modemResultFuture = new CompletableFuture<>();
+        try {
+            Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: overriding DDS for "
+                    + extensionTime + "seconds");
+            PhoneSwitcher.getInstance().overrideDefaultDataForEmergency(phone.getPhoneId(),
+                    extensionTime, modemResultFuture);
+            // Catch all exceptions, we want to continue with emergency call if possible.
+        } catch (Exception e) {
+            Log.w(this, "possiblyOverrideDefaultDataForEmergencyCall: exception = "
+                    + e.getMessage());
+        }
+        return modemResultFuture;
+    }
+
     /**
      * Get the Phone to use for an emergency call of the given emergency number address:
      *  a) If there are multiple Phones with the Subscriptions that support the emergency number
diff --git a/tests/src/com/android/services/telephony/ImsConferenceTest.java b/tests/src/com/android/services/telephony/ImsConferenceTest.java
index 56a6240..909ab65 100644
--- a/tests/src/com/android/services/telephony/ImsConferenceTest.java
+++ b/tests/src/com/android/services/telephony/ImsConferenceTest.java
@@ -32,6 +32,7 @@
 import android.os.Looper;
 import android.telecom.ConferenceParticipant;
 import android.telecom.Connection;
+import android.telecom.PhoneAccountHandle;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.telephony.PhoneConstants;
@@ -53,9 +54,6 @@
     @Mock
     private TelecomAccountRegistry mMockTelecomAccountRegistry;
 
-    @Mock
-    private com.android.internal.telephony.Connection mOriginalConnection;
-
     private TestTelephonyConnection mConferenceHost;
 
     @Before
@@ -66,11 +64,16 @@
         }
         mConferenceHost = new TestTelephonyConnection();
         mConferenceHost.setManageImsConferenceCallSupported(true);
+        when(mMockTelecomAccountRegistry.getAddress(any(PhoneAccountHandle.class)))
+                .thenReturn(null);
     }
 
     @Test
     @SmallTest
     public void testSinglePartyEmulation() {
+        when(mMockTelecomAccountRegistry.isUsingSimCallManager(any(PhoneAccountHandle.class)))
+                .thenReturn(false);
+
         ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
                 mMockTelephonyConnectionServiceProxy, mConferenceHost,
                 null /* phoneAccountHandle */, () -> true /* featureFlagProxy */);
@@ -100,9 +103,51 @@
         assertEquals(2, imsConference.getNumberOfParticipants());
     }
 
+    /**
+     * Verify that we do not use single party emulation when a sim call manager is in use.
+     */
+    @Test
+    @SmallTest
+    public void testNoSinglePartyEmulationWithSimCallManager() {
+        // Make it look like there is a sim call manager in use.
+        when(mMockTelecomAccountRegistry.isUsingSimCallManager(
+                any(PhoneAccountHandle.class))).thenReturn(true);
+
+        ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
+                mMockTelephonyConnectionServiceProxy, mConferenceHost,
+                null /* phoneAccountHandle */, () -> true /* featureFlagProxy */);
+
+        ConferenceParticipant participant1 = new ConferenceParticipant(
+                Uri.parse("tel:6505551212"),
+                "A",
+                Uri.parse("sip:6505551212@testims.com"),
+                Connection.STATE_ACTIVE);
+        ConferenceParticipant participant2 = new ConferenceParticipant(
+                Uri.parse("tel:6505551213"),
+                "A",
+                Uri.parse("sip:6505551213@testims.com"),
+                Connection.STATE_ACTIVE);
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+                Arrays.asList(participant1, participant2));
+        assertEquals(2, imsConference.getNumberOfParticipants());
+
+        // Because we're not using single party emulation, should still be one participant.
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+                Arrays.asList(participant1));
+        assertEquals(1, imsConference.getNumberOfParticipants());
+
+        // Back to 2!
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+                Arrays.asList(participant1, participant2));
+        assertEquals(2, imsConference.getNumberOfParticipants());
+    }
+
     @Test
     @SmallTest
     public void testNormalConference() {
+        when(mMockTelecomAccountRegistry.isUsingSimCallManager(any(PhoneAccountHandle.class)))
+                .thenReturn(false);
+
         ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
                 mMockTelephonyConnectionServiceProxy, mConferenceHost,
                 null /* phoneAccountHandle */, () -> false /* featureFlagProxy */);
diff --git a/tests/src/com/android/services/telephony/TestTelephonyConnection.java b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
index c064ef6..b6e4bf3 100644
--- a/tests/src/com/android/services/telephony/TestTelephonyConnection.java
+++ b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
@@ -82,9 +82,11 @@
                 any(Connection.PostDialListener.class));
         when(mMockPhone.getRingingCall()).thenReturn(mMockCall);
         when(mMockPhone.getContext()).thenReturn(mMockContext);
+        when(mMockPhone.getCurrentSubscriberUris()).thenReturn(null);
         when(mMockContext.getResources()).thenReturn(mMockResources);
         when(mMockResources.getBoolean(anyInt())).thenReturn(false);
         when(mMockPhone.getDefaultPhone()).thenReturn(mMockPhone);
+        when(mMockPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_IMS);
         when(mMockCall.getState()).thenReturn(Call.State.ACTIVE);
         when(mMockCall.getPhone()).thenReturn(mMockPhone);
     }