[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: 06060f87c8 -s ours

am skip reason: subject contains skip directive

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Telephony/+/20033432

Change-Id: Ia46112a02fa551260fd94fffb3e9fa95dc63a627
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8218a84..d0f427c 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -207,8 +207,6 @@
     <style name="DialerAlertDialogTheme"
         parent="@android:style/Theme.Material.Light.Dialog">
         <item name="android:forceDarkAllowed">true</item>
-        <item name="android:colorAccent">@color/dialer_theme_color</item>
-        <item name="android:textColor">?android:attr/textColorPrimaryInverseDisableOnly</item>
     </style>
 
     <style name="Empty" parent="@android:style/Theme.Material.Light">
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index 7bff98a..fdd610e 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -456,11 +456,7 @@
                     CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL,
                     false);
         boolean isDataEnabled;
-        if (mPhone.isUsingNewDataStack()) {
-            isDataEnabled = mPhone.getDataSettingsManager().isDataEnabled();
-        } else {
-            isDataEnabled = mPhone.getDataEnabledSettings().isDataEnabled();
-        }
+        isDataEnabled = mPhone.getDataSettingsManager().isDataEnabled();
         if (mImsMgr.isVtEnabledByPlatform() && mImsMgr.isVtProvisionedOnDevice()
                 && (carrierConfig.getBoolean(
                         CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS)
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index 307170a..5d9928e 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -1170,12 +1170,24 @@
         });
         if (packageFiles == null || packageFiles.length < 1) return false;
         for (File f : packageFiles) {
-            logd("Deleting " + f.getName());
+            logd("Deleting " + getFilePathForLogging(f.getName()));
             f.delete();
         }
         return true;
     }
 
+    private String getFilePathForLogging(String filePath) {
+        if (!TextUtils.isEmpty(filePath)) {
+            String[] fileTokens = filePath.split("-");
+            if (fileTokens != null && fileTokens.length > 2) {
+                String iccid = fileTokens[fileTokens.length -2];
+                return getFilePathForLogging(filePath, iccid);
+            }
+            return filePath;
+        }
+        return filePath;
+    }
+
     /** Builds a canonical file name for a config file. */
     @NonNull
     private static String getFilenameForConfig(
@@ -1321,7 +1333,6 @@
             boolean persistent) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.MODIFY_PHONE_STATE, null);
-        //TODO: Also check for SHELL UID to restrict this method to testing only (b/131326259)
         int phoneId = SubscriptionManager.getPhoneId(subscriptionId);
         if (!SubscriptionManager.isValidPhoneId(phoneId)) {
             logd("Ignore invalid phoneId: " + phoneId + " for subId: " + subscriptionId);
diff --git a/src/com/android/phone/ImsProvisioningController.java b/src/com/android/phone/ImsProvisioningController.java
index 696f567..6a6b155 100644
--- a/src/com/android/phone/ImsProvisioningController.java
+++ b/src/com/android/phone/ImsProvisioningController.java
@@ -41,6 +41,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.os.AsyncResult;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -55,6 +56,7 @@
 import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.aidl.IFeatureProvisioningCallback;
 import android.telephony.ims.aidl.IImsConfig;
+import android.telephony.ims.aidl.IImsConfigCallback;
 import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities;
 import android.telephony.ims.feature.RcsFeature.RcsImsCapabilities;
 import android.telephony.ims.stub.ImsConfigImplBase;
@@ -89,6 +91,7 @@
     private static final int EVENT_PROVISIONING_CAPABILITY_CHANGED = 2;
     @VisibleForTesting
     protected static final int EVENT_MULTI_SIM_CONFIGURATION_CHANGE = 3;
+    private static final int EVENT_PROVISIONING_VALUE_CHANGED = 4;
 
     // Provisioning Keys that are handled via AOSP cache and not sent to the ImsService
     private static final int[] LOCAL_IMS_CONFIG_KEYS = {
@@ -245,6 +248,11 @@
                     int activeModemCount = (int) ((AsyncResult) msg.obj).result;
                     onMultiSimConfigChanged(activeModemCount);
                     break;
+                case EVENT_PROVISIONING_VALUE_CHANGED:
+                    log("subId " + msg.arg1 + " changed provisioning value item : " + msg.arg2
+                            + " value : " + (int) msg.obj);
+                    updateCapabilityTechFromKey(msg.arg1, msg.arg2, (int) msg.obj);
+                    break;
                 default:
                     log("unknown message " + msg);
                     break;
@@ -366,12 +374,15 @@
         private boolean mRequiredNotify = false;
         private int mSubId;
         private int mSlotId;
+        private ConfigCallback mConfigCallback;
 
         MmTelFeatureListener(int slotId) {
             log(LOG_PREFIX, slotId, "created");
 
             mSlotId = slotId;
             mSubId = getSubId(slotId);
+            mConfigCallback = new ConfigCallback(mSubId);
+
             mConnector = mMmTelFeatureConnector.create(
                     mApp, slotId, TAG, this, new HandlerExecutor(mHandler));
             mConnector.connect();
@@ -389,10 +400,22 @@
 
             mSubId = subId;
             mSlotId = getSlotId(subId);
+            mConfigCallback.setSubId(subId);
         }
 
         public void destroy() {
             log("destroy");
+            if (mImsManager != null) {
+                try {
+                    ImsConfig imsConfig = getImsConfig(mImsManager);
+                    if (imsConfig != null) {
+                        imsConfig.removeConfigCallback(mConfigCallback);
+                    }
+                } catch (ImsException e) {
+                    logw(LOG_PREFIX, mSlotId, "destroy : " + e.getMessage());
+                }
+            }
+            mConfigCallback = null;
             mConnector.disconnect();
             mConnector = null;
             mReady = false;
@@ -409,6 +432,17 @@
             mReady = true;
             mImsManager = manager;
 
+            if (mImsManager != null) {
+                try {
+                    ImsConfig imsConfig = getImsConfig(mImsManager);
+                    if (imsConfig != null) {
+                        imsConfig.addConfigCallback(mConfigCallback);
+                    }
+                } catch (ImsException e) {
+                    logw(LOG_PREFIX, mSlotId, "addConfigCallback : " + e.getMessage());
+                }
+            }
+
             onMmTelAvailable();
         }
 
@@ -572,12 +606,15 @@
         private boolean mRequiredNotify = false;
         private int mSubId;
         private int mSlotId;
+        private ConfigCallback mConfigCallback;
 
         RcsFeatureListener(int slotId) {
             log(LOG_PREFIX, slotId, "created");
 
             mSlotId = slotId;
             mSubId = getSubId(slotId);
+            mConfigCallback = new ConfigCallback(mSubId);
+
             mConnector = mRcsFeatureConnector.create(
                     mApp, slotId, this, new HandlerExecutor(mHandler), TAG);
             mConnector.connect();
@@ -595,10 +632,22 @@
 
             mSubId = subId;
             mSlotId = getSlotId(subId);
+            mConfigCallback.setSubId(subId);
         }
 
         public void destroy() {
             log(LOG_PREFIX, mSlotId, "destroy");
+            if (mRcsFeatureManager != null) {
+                try {
+                    ImsConfig imsConfig = getImsConfig(mRcsFeatureManager.getConfig());
+                    if (imsConfig != null) {
+                        imsConfig.removeConfigCallback(mConfigCallback);
+                    }
+                } catch (ImsException e) {
+                    logw(LOG_PREFIX, mSlotId, "destroy :" + e.getMessage());
+                }
+            }
+            mConfigCallback = null;
             mConnector.disconnect();
             mConnector = null;
             mReady = false;
@@ -611,6 +660,17 @@
             mReady = true;
             mRcsFeatureManager = manager;
 
+            if (mRcsFeatureManager != null) {
+                try {
+                    ImsConfig imsConfig = getImsConfig(mRcsFeatureManager.getConfig());
+                    if (imsConfig != null) {
+                        imsConfig.addConfigCallback(mConfigCallback);
+                    }
+                } catch (ImsException e) {
+                    logw(LOG_PREFIX, mSlotId, "addConfigCallback :" + e.getMessage());
+                }
+            }
+
             onRcsAvailable();
         }
 
@@ -726,6 +786,42 @@
         }
     }
 
+    // When vendor ImsService changed provisioning data, which should be updated in AOSP.
+    // Catch the event using IImsConfigCallback.
+    private final class ConfigCallback extends IImsConfigCallback.Stub {
+        private int mSubId;
+
+        ConfigCallback(int subId) {
+            mSubId = subId;
+        }
+
+        public void setSubId(int subId) {
+            mSubId = subId;
+        }
+
+        @Override
+        public void onIntConfigChanged(int item, int value) throws RemoteException {
+            if (!Arrays.stream(LOCAL_IMS_CONFIG_KEYS).anyMatch(keyValue -> keyValue == item)) {
+                return;
+            }
+
+            final long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                if (mHandler != null) {
+                    mHandler.sendMessage(mHandler.obtainMessage(
+                            EVENT_PROVISIONING_VALUE_CHANGED, mSubId, item, (Object) value));
+                }
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        @Override
+        public void onStringConfigChanged(int item, String value) throws RemoteException {
+            // Ignore this callback.
+        }
+    }
+
     /**
      * Do NOT use this directly, instead use {@link #getInstance()}.
      */
diff --git a/src/com/android/phone/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java
index bf55764..d4a0f1e 100644
--- a/src/com/android/phone/ImsRcsController.java
+++ b/src/com/android/phone/ImsRcsController.java
@@ -252,7 +252,7 @@
         } catch (ImsException e) {
             Log.e(TAG, "isCapable: sudId=" + subId
                     + ", capability=" + capability + ", " + e.getMessage());
-            return false;
+            throw new ServiceSpecificException(e.getCode(), e.getMessage());
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -278,7 +278,7 @@
         } catch (ImsException e) {
             Log.e(TAG, "isAvailable: sudId=" + subId
                     + ", capability=" + capability + ", " + e.getMessage());
-            return false;
+            throw new ServiceSpecificException(e.getCode(), e.getMessage());
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -786,16 +786,43 @@
         int slotId = phone.getPhoneId();
         if (!skipVerifyingConfig) {
             verifyImsRcsConfiguredOrThrow(slotId);
+            verifyRcsSubIdActiveOrThrow(slotId, subId);
         }
         RcsFeatureController c = mRcsService.getFeatureController(slotId);
         if (c == null) {
+            // If we hit this case, we have verified that TelephonyRcsService has processed any
+            // subId changes for the associated slot and applied configs. In this case, the configs
+            // do not have the RCS feature enabled.
             throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
                     "The requested operation is not supported for subId " + subId);
         }
+        if (!skipVerifyingConfig && c.getAssociatedSubId() != subId) {
+            // If we hit this case, the ImsFeature has not finished setting up the RCS feature yet
+            // or the RCS feature has crashed and is being set up again.
+            Log.w(TAG, "getRcsFeatureController: service unavailable on slot " + slotId
+                    + " for subId " + subId);
+            throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
+                    "The ImsService is not currently available for subid " + subId
+                            + ", please try again");
+        }
         return c;
     }
 
     /**
+     * Ensure the TelephonyRcsService is tracking the supplied subId for the supplied slotId and has
+     * set up the stack.
+     */
+    private void verifyRcsSubIdActiveOrThrow(int slotId, int subId) {
+        if (mRcsService.verifyActiveSubId(slotId, subId)) return;
+
+        Log.w(TAG, "verifyRcsSubIdActiveOrThrow: verify failed, service not set up yet on "
+                + "slot " + slotId + " for subId " + subId);
+        throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
+                "ImsService set up in progress for subId " + subId
+                        + ", please try again");
+    }
+
+    /**
      * Throw an ImsException if the IMS resolver does not have an ImsService configured for RCS
      * for the given slot ID or no ImsResolver instance has been created.
      * @param slotId The slot ID that the IMS service is created for.
diff --git a/src/com/android/phone/ImsStateCallbackController.java b/src/com/android/phone/ImsStateCallbackController.java
index 57c1787..edad754 100644
--- a/src/com/android/phone/ImsStateCallbackController.java
+++ b/src/com/android/phone/ImsStateCallbackController.java
@@ -68,6 +68,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 
 /**
@@ -143,6 +144,10 @@
     private final SparseArray<MmTelFeatureListener> mMmTelFeatureListeners = new SparseArray<>();
     private final SparseArray<RcsFeatureListener> mRcsFeatureListeners = new SparseArray<>();
 
+    // Container to store ImsManager instance by subId
+    private final ConcurrentHashMap<Integer, ImsManager> mSubIdToImsManagerCache =
+            new ConcurrentHashMap<>();
+
     private final SubscriptionManager mSubscriptionManager;
     private final TelephonyRegistryManager mTelephonyRegistryManager;
     private MmTelFeatureConnectorFactory mMmTelFeatureFactory;
@@ -282,6 +287,13 @@
             if (mSubId == subId) return;
             logd(mLogPrefix + "setSubId changed subId=" + subId);
 
+            // subId changed from valid to invalid
+            if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                if (VDBG) logv(mLogPrefix + "setSubId remove ImsManager " + mSubId);
+                // remove ImsManager reference associated with subId
+                mSubIdToImsManagerCache.remove(mSubId);
+            }
+
             mSubId = subId;
         }
 
@@ -298,6 +310,12 @@
             mSubId = subId;
             if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) return;
 
+            // store ImsManager reference associated with subId
+            if (manager != null) {
+                if (VDBG) logv(mLogPrefix + "connectionReady add ImsManager " + subId);
+                mSubIdToImsManagerCache.put(subId, manager);
+            }
+
             mState = STATE_READY;
             mReason = AVAILABLE;
             mHasConfig = true;
@@ -311,6 +329,10 @@
             reason = convertReasonType(reason);
             if (mReason == reason) return;
 
+            // remove ImsManager reference associated with subId
+            if (VDBG) logv(mLogPrefix + "connectionUnavailable remove ImsManager " + mSubId);
+            mSubIdToImsManagerCache.remove(mSubId);
+
             connectionUnavailableInternal(reason);
         }
 
@@ -319,7 +341,7 @@
             mReason = reason;
 
             /* If having no IMS package for MMTEL,
-             * dicard the reason except REASON_NO_IMS_SERVICE_CONFIGURED. */
+             * discard the reason except REASON_NO_IMS_SERVICE_CONFIGURED. */
             if (!mHasConfig && reason != REASON_NO_IMS_SERVICE_CONFIGURED) return;
 
             onFeatureStateChange(mSubId, FEATURE_MMTEL, mState, mReason);
@@ -973,6 +995,19 @@
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_CALLBACK, cb));
     }
 
+    /**
+     * Get ImsManager reference associated with subId
+     *
+     * @param subId subscribe ID
+     * @return instance of ImsManager associated with subId, but if ImsService is not
+     * available return null
+     */
+    public ImsManager getImsManager(int subId) {
+        if (VDBG) logv("getImsManager subId = " + subId);
+
+        return mSubIdToImsManagerCache.get(subId);
+    }
+
     private void removeInactiveCallbacks(
             ArrayList<IBinder> inactiveCallbacks, String message) {
         if (inactiveCallbacks == null || inactiveCallbacks.size() == 0) return;
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index f2641a1..27e1606 100644
--- a/src/com/android/phone/NotificationMgr.java
+++ b/src/com/android/phone/NotificationMgr.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.READ_PHONE_STATE;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.BroadcastOptions;
 import android.app.Notification;
@@ -47,6 +48,7 @@
 import android.telecom.TelecomManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.RadioAccessFamily;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -57,8 +59,10 @@
 import android.util.SparseArray;
 import android.widget.Toast;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.TelephonyCapabilities;
 import com.android.internal.telephony.util.NotificationChannelController;
 import com.android.phone.settings.VoicemailSettingsActivity;
@@ -163,7 +167,8 @@
      * Private constructor (this is a singleton).
      * @see #init(PhoneGlobals)
      */
-    private NotificationMgr(PhoneGlobals app) {
+    @VisibleForTesting
+    /* package */ NotificationMgr(PhoneGlobals app) {
         mApp = app;
         mContext = app;
         mStatusBarManager =
@@ -895,15 +900,22 @@
         Log.i(LOG_TAG, msg);
     }
 
-    /**
-     * In case network selection notification shows up repeatedly under
-     * unstable network condition. The logic is to check whether or not
-     * the service state keeps in no service condition for at least
-     * {@link #NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS}.
-     * And checking {@link #NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIMES} times.
-     * To avoid the notification showing up for the momentary state.
-     */
     private void shouldShowNotification(int serviceState, int subId) {
+        // "Network selection unavailable" notification should only show when network selection is
+        // visible to the end user. Some CC items e.g. KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL
+        // can be overridden to hide the network selection to the end user. In this case, the
+        // notification is not shown to avoid confusion to the end user.
+        if (!shouldDisplayNetworkSelectOptions(subId)) {
+            logi("Carrier configs refuse to show network selection not available notification");
+            return;
+        }
+
+        // In case network selection notification shows up repeatedly under
+        // unstable network condition. The logic is to check whether or not
+        // the service state keeps in no service condition for at least
+        // {@link #NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS}.
+        // And checking {@link #NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIMES} times.
+        // To avoid the notification showing up for the momentary state.
         if (serviceState == ServiceState.STATE_OUT_OF_SERVICE) {
             if (mPreviousServiceState.get(subId, STATE_UNKNOWN_SERVICE)
                     != ServiceState.STATE_OUT_OF_SERVICE) {
@@ -930,6 +942,113 @@
         }
     }
 
+    // TODO(b/243010310): merge methods below with Settings#MobileNetworkUtils and optimize them.
+    // The methods below are copied from com.android.settings.network.telephony.MobileNetworkUtils
+    // to make sure the network selection unavailable notification should not show when Network
+    // Selection menu is not present in Settings app.
+    private boolean shouldDisplayNetworkSelectOptions(int subId) {
+        final TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(subId);
+        final CarrierConfigManager carrierConfigManager = mContext.getSystemService(
+                CarrierConfigManager.class);
+        final PersistableBundle carrierConfig = carrierConfigManager.getConfigForSubId(subId);
+
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                || carrierConfig == null
+                || !carrierConfig.getBoolean(
+                CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL)
+                || carrierConfig.getBoolean(
+                CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL)
+                || (carrierConfig.getBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL)
+                && !telephonyManager.isManualNetworkSelectionAllowed())) {
+            return false;
+        }
+
+        if (isWorldMode(carrierConfig)) {
+            final int networkMode = RadioAccessFamily.getNetworkTypeFromRaf(
+                    (int) telephonyManager.getAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER));
+            if (networkMode == RILConstants.NETWORK_MODE_LTE_CDMA_EVDO) {
+                return false;
+            }
+            if (shouldSpeciallyUpdateGsmCdma(telephonyManager, carrierConfig)) {
+                return false;
+            }
+            if (networkMode == RILConstants.NETWORK_MODE_LTE_GSM_WCDMA) {
+                return true;
+            }
+        }
+
+        return isGsmBasicOptions(telephonyManager, carrierConfig);
+    }
+
+    private static boolean isWorldMode(@NonNull PersistableBundle carrierConfig) {
+        return carrierConfig.getBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL);
+    }
+
+    private static boolean shouldSpeciallyUpdateGsmCdma(@NonNull TelephonyManager telephonyManager,
+            @NonNull PersistableBundle carrierConfig) {
+        if (!isWorldMode(carrierConfig)) {
+            return false;
+        }
+
+        final int networkMode = RadioAccessFamily.getNetworkTypeFromRaf(
+                (int) telephonyManager.getAllowedNetworkTypesForReason(
+                        TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER));
+        if (networkMode == RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM
+                || networkMode == RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA
+                || networkMode == RILConstants.NETWORK_MODE_LTE_TDSCDMA
+                || networkMode == RILConstants.NETWORK_MODE_LTE_TDSCDMA_WCDMA
+                || networkMode
+                == RILConstants.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA
+                || networkMode == RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA) {
+            if (!isTdscdmaSupported(telephonyManager, carrierConfig)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private static boolean isTdscdmaSupported(@NonNull TelephonyManager telephonyManager,
+            @NonNull PersistableBundle carrierConfig) {
+        if (carrierConfig.getBoolean(CarrierConfigManager.KEY_SUPPORT_TDSCDMA_BOOL)) {
+            return true;
+        }
+        final String[] numericArray = carrierConfig.getStringArray(
+                CarrierConfigManager.KEY_SUPPORT_TDSCDMA_ROAMING_NETWORKS_STRING_ARRAY);
+        if (numericArray == null) {
+            return false;
+        }
+        final ServiceState serviceState = telephonyManager.getServiceState();
+        final String operatorNumeric =
+                (serviceState != null) ? serviceState.getOperatorNumeric() : null;
+        if (operatorNumeric == null) {
+            return false;
+        }
+        for (String numeric : numericArray) {
+            if (operatorNumeric.equals(numeric)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean isGsmBasicOptions(@NonNull TelephonyManager telephonyManager,
+            @NonNull PersistableBundle carrierConfig) {
+        if (!carrierConfig.getBoolean(
+                CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL)
+                && carrierConfig.getBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL)) {
+            return true;
+        }
+
+        if (telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
+            return true;
+        }
+
+        return false;
+    }
+    // END of TODO:(b/243010310): merge methods above with Settings#MobileNetworkUtils and optimize.
+
     private void startPendingNetworkSelectionNotification(int subId) {
         if (!mHandler.hasMessages(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId)) {
             if (DBG) {
diff --git a/src/com/android/phone/PhoneDisplayMessage.java b/src/com/android/phone/PhoneDisplayMessage.java
index be7fc7f..c487cba 100644
--- a/src/com/android/phone/PhoneDisplayMessage.java
+++ b/src/com/android/phone/PhoneDisplayMessage.java
@@ -80,7 +80,8 @@
         sDisplayMessageDialog.getWindow().addFlags(
                 WindowManager.LayoutParams.FLAG_DIM_BEHIND
                 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
-                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
 
         sDisplayMessageDialog.show();
     }
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 99e73ff..abbd816 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -49,7 +49,6 @@
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyLocalConnection;
 import android.telephony.TelephonyManager;
-import android.telephony.data.ApnSetting;
 import android.util.LocalLog;
 import android.util.Log;
 import android.widget.Toast;
@@ -68,8 +67,6 @@
 import com.android.internal.telephony.TelephonyComponentFactory;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.data.DataEvaluation.DataDisallowedReason;
-import com.android.internal.telephony.dataconnection.DataConnectionReasons;
-import com.android.internal.telephony.dataconnection.DataConnectionReasons.DataDisallowedReasonType;
 import com.android.internal.telephony.ims.ImsResolver;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
@@ -549,15 +546,15 @@
                     mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED, null);
 
             mTelephonyCallbacks = new PhoneAppCallback[tm.getSupportedModemCount()];
-
-            for (Phone phone : PhoneFactory.getPhones()) {
-                int subId = phone.getSubId();
-                PhoneAppCallback callback = new PhoneAppCallback(subId);
-                tm.createForSubscriptionId(subId).registerTelephonyCallback(
-                        TelephonyManager.INCLUDE_LOCATION_DATA_NONE, mHandler::post, callback);
-                mTelephonyCallbacks[phone.getPhoneId()] = callback;
+            if (tm.getSupportedModemCount() > 0) {
+                for (Phone phone : PhoneFactory.getPhones()) {
+                    int subId = phone.getSubId();
+                    PhoneAppCallback callback = new PhoneAppCallback(subId);
+                    tm.createForSubscriptionId(subId).registerTelephonyCallback(
+                            TelephonyManager.INCLUDE_LOCATION_DATA_NONE, mHandler::post, callback);
+                    mTelephonyCallbacks[phone.getPhoneId()] = callback;
+                }
             }
-
             mCarrierVvmPackageInstalledReceiver.register(this);
 
             //set the default values for the preferences in the phone.
@@ -891,22 +888,13 @@
 
         boolean dataAllowed;
         boolean notAllowedDueToRoamingOff;
-        if (phone.isUsingNewDataStack()) {
-            List<DataDisallowedReason> reasons = phone.getDataNetworkController()
-                    .getInternetDataDisallowedReasons();
-            dataAllowed = reasons.isEmpty();
-            notAllowedDueToRoamingOff = (reasons.size() == 1
-                    && reasons.contains(DataDisallowedReason.ROAMING_DISABLED));
-            mDataRoamingNotifLog.log("dataAllowed=" + dataAllowed + ", reasons=" + reasons);
-            if (VDBG) Log.v(LOG_TAG, "dataAllowed=" + dataAllowed + ", reasons=" + reasons);
-        } else {
-            DataConnectionReasons reasons = new DataConnectionReasons();
-            dataAllowed = phone.isDataAllowed(ApnSetting.TYPE_DEFAULT, reasons);
-            notAllowedDueToRoamingOff = reasons.containsOnly(
-                    DataDisallowedReasonType.ROAMING_DISABLED);
-            mDataRoamingNotifLog.log("dataAllowed=" + dataAllowed + ", reasons=" + reasons);
-            if (VDBG) Log.v(LOG_TAG, "dataAllowed=" + dataAllowed + ", reasons=" + reasons);
-        }
+        List<DataDisallowedReason> reasons = phone.getDataNetworkController()
+                .getInternetDataDisallowedReasons();
+        dataAllowed = reasons.isEmpty();
+        notAllowedDueToRoamingOff = (reasons.size() == 1
+                && reasons.contains(DataDisallowedReason.ROAMING_DISABLED));
+        mDataRoamingNotifLog.log("dataAllowed=" + dataAllowed + ", reasons=" + reasons);
+        if (VDBG) Log.v(LOG_TAG, "dataAllowed=" + dataAllowed + ", reasons=" + reasons);
 
         if (!dataAllowed && notAllowedDueToRoamingOff) {
             // No need to show it again if we never cancelled it explicitly.
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 66cef64..eb162e6 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -77,6 +77,7 @@
 import android.telephony.Annotation.ApnType;
 import android.telephony.Annotation.DataActivityType;
 import android.telephony.Annotation.ThermalMitigationResult;
+import android.telephony.AnomalyReporter;
 import android.telephony.CallForwardingInfo;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CarrierRestrictionRules;
@@ -184,7 +185,6 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyPermissions;
 import com.android.internal.telephony.data.DataUtils;
-import com.android.internal.telephony.dataconnection.ApnSettingUtils;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.euicc.EuiccConnector;
 import com.android.internal.telephony.ims.ImsResolver;
@@ -210,6 +210,7 @@
 import com.android.phone.callcomposer.CallComposerPictureTransfer;
 import com.android.phone.callcomposer.ImageData;
 import com.android.phone.settings.PickSmsSubscriptionActivity;
+import com.android.phone.slicestore.SliceStore;
 import com.android.phone.vvm.PhoneAccountHandleConverter;
 import com.android.phone.vvm.RemoteVvmTaskManager;
 import com.android.phone.vvm.VisualVoicemailSettingsUtil;
@@ -235,6 +236,7 @@
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
@@ -358,6 +360,8 @@
     private static final int EVENT_ENABLE_VONR_DONE = 114;
     private static final int CMD_IS_VONR_ENABLED = 115;
     private static final int EVENT_IS_VONR_ENABLED_DONE = 116;
+    private static final int CMD_PURCHASE_PREMIUM_CAPABILITY = 117;
+    private static final int EVENT_PURCHASE_PREMIUM_CAPABILITY_DONE = 118;
 
     // Parameters of select command.
     private static final int SELECT_COMMAND = 0xA4;
@@ -406,6 +410,9 @@
     private static final int SET_DATA_THROTTLING_MODEM_THREW_INVALID_PARAMS = -1;
     private static final int MODEM_DOES_NOT_SUPPORT_DATA_THROTTLING_ERROR_CODE = -2;
 
+    private static final String PURCHASE_PREMIUM_CAPABILITY_ERROR_UUID =
+            "24bf97a6-e8a6-44d8-a6a4-255d7548733c";
+
     /**
      * Experiment flag to enable erase modem config on reset network, default value is false
      */
@@ -2133,6 +2140,36 @@
                     break;
                 }
 
+                case CMD_PURCHASE_PREMIUM_CAPABILITY:
+                    request = (MainThreadRequest) msg.obj;
+                    onCompleted = obtainMessage(EVENT_PURCHASE_PREMIUM_CAPABILITY_DONE, request);
+                    SliceStore.getInstance(request.phone).purchasePremiumCapability(
+                            ((Pair<Integer, IIntegerConsumer>) request.argument).first,
+                            onCompleted);
+                    break;
+
+                case EVENT_PURCHASE_PREMIUM_CAPABILITY_DONE:
+                    ar = (AsyncResult) msg.obj;
+                    request = (MainThreadRequest) ar.userObj;
+                    Pair<Integer, IIntegerConsumer> pair =
+                            (Pair<Integer, IIntegerConsumer>) request.argument;
+                    try {
+                        int result = (int) ar.result;
+                        pair.second.accept(result);
+                        log("purchasePremiumCapability: capability="
+                                + TelephonyManager.convertPremiumCapabilityToString(pair.first)
+                                + ", result= "
+                                + TelephonyManager.convertPurchaseResultToString(result));
+                    } catch (RemoteException e) {
+                        String logStr = "Purchase premium capability "
+                                + TelephonyManager.convertPremiumCapabilityToString(pair.first)
+                                + " failed: " + e;
+                        if (DBG) log(logStr);
+                        AnomalyReporter.reportAnomaly(
+                                UUID.fromString(PURCHASE_PREMIUM_CAPABILITY_ERROR_UUID), logStr);
+                    }
+                    break;
+
                 case CMD_PREPARE_UNATTENDED_REBOOT:
                     request = (MainThreadRequest) msg.obj;
                     request.result =
@@ -2851,6 +2888,95 @@
         }
     }
 
+    /**
+     * Vote on powering off the radio for a reason. The radio will be turned on only when there is
+     * no reason to power it off. When any of the voters want to power it off, it will be turned
+     * off. In case of emergency, the radio will be turned on even if there are some reasons for
+     * powering it off, and these radio off votes will be cleared.
+     * Multiple apps can vote for the same reason and the last vote will take effect. Each app is
+     * responsible for its vote. A powering-off vote of a reason will be maintained until it is
+     * cleared by calling {@link clearRadioPowerOffForReason} for that reason, or an emergency call
+     * is made, or the device is rebooted. When an app comes backup from a crash, it needs to make
+     * sure if its vote is as expected. An app can use the API {@link getRadioPowerOffReasons} to
+     * check its vote.
+     *
+     * @param subId The subscription ID.
+     * @param reason The reason for powering off radio.
+     * @return true on success and false on failure.
+     */
+    public boolean requestRadioPowerOffForReason(int subId,
+            @TelephonyManager.RadioPowerReason int reason) {
+        enforceModifyPermission();
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final Phone phone = getPhone(subId);
+            if (phone != null) {
+                phone.setRadioPowerForReason(false, reason);
+                return true;
+            } else {
+                return false;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Remove the vote on powering off the radio for a reason, as requested by
+     * {@link requestRadioPowerOffForReason}.
+     *
+     * @param subId The subscription ID.
+     * @param reason The reason for powering off radio.
+     * @return true on success and false on failure.
+     */
+    public boolean clearRadioPowerOffForReason(int subId,
+            @TelephonyManager.RadioPowerReason int reason) {
+        enforceModifyPermission();
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final Phone phone = getPhone(subId);
+            if (phone != null) {
+                phone.setRadioPowerForReason(true, reason);
+                return true;
+            } else {
+                return false;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Get reasons for powering off radio, as requested by {@link requestRadioPowerOffForReason}.
+     *
+     * @param subId The subscription ID.
+     * @param callingPackage The package making the call.
+     * @param callingFeatureId The feature in the package.
+     * @return List of reasons for powering off radio.
+     */
+    public List getRadioPowerOffReasons(int subId, String callingPackage, String callingFeatureId) {
+        enforceReadPrivilegedPermission("getRadioPowerOffReasons");
+
+        final long identity = Binder.clearCallingIdentity();
+        List result = new ArrayList();
+        try {
+            if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mApp, subId,
+                    callingPackage, callingFeatureId, "getRadioPowerOffReasons")) {
+                return result;
+            }
+
+            final Phone phone = getPhone(subId);
+            if (phone != null) {
+                result.addAll(phone.getRadioPowerOffReasons());
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return result;
+    }
+
     // FIXME: subId version needed
     @Override
     public boolean enableDataConnectivity(String callingPackage) {
@@ -2861,13 +2987,8 @@
             int subId = mSubscriptionController.getDefaultDataSubId();
             final Phone phone = getPhone(subId);
             if (phone != null) {
-                if (phone.isUsingNewDataStack()) {
-                    phone.getDataSettingsManager().setDataEnabled(
-                            TelephonyManager.DATA_ENABLED_REASON_USER, true, callingPackage);
-                } else {
-                    phone.getDataEnabledSettings().setDataEnabled(
-                            TelephonyManager.DATA_ENABLED_REASON_USER, true);
-                }
+                phone.getDataSettingsManager().setDataEnabled(
+                        TelephonyManager.DATA_ENABLED_REASON_USER, true, callingPackage);
                 return true;
             } else {
                 return false;
@@ -2887,13 +3008,8 @@
             int subId = mSubscriptionController.getDefaultDataSubId();
             final Phone phone = getPhone(subId);
             if (phone != null) {
-                if (phone.isUsingNewDataStack()) {
-                    phone.getDataSettingsManager().setDataEnabled(
-                            TelephonyManager.DATA_ENABLED_REASON_USER, false, callingPackage);
-                } else {
-                    phone.getDataEnabledSettings().setDataEnabled(
-                            TelephonyManager.DATA_ENABLED_REASON_USER, false);
-                }
+                phone.getDataSettingsManager().setDataEnabled(
+                        TelephonyManager.DATA_ENABLED_REASON_USER, false, callingPackage);
                 return true;
             } else {
                 return false;
@@ -3009,10 +3125,7 @@
         try {
             final Phone phone = getPhone(subId);
             if (phone != null) {
-                if (phone.isUsingNewDataStack()) {
-                    return phone.getDataNetworkController().getInternetDataNetworkState();
-                }
-                return PhoneConstantConversions.convertDataState(phone.getDataConnectionState());
+                return phone.getDataNetworkController().getInternetDataNetworkState();
             } else {
                 return PhoneConstantConversions.convertDataState(
                         PhoneConstants.DataState.DISCONNECTED);
@@ -3278,13 +3391,18 @@
     }
 
     @Override
-    public void setCellInfoListRate(int rateInMillis) {
+    public void setCellInfoListRate(int rateInMillis, int subId) {
         enforceModifyPermission();
         WorkSource workSource = getWorkSource(Binder.getCallingUid());
 
         final long identity = Binder.clearCallingIdentity();
         try {
-            getDefaultPhone().setCellInfoListRate(rateInMillis, workSource);
+            Phone phone = getPhone(subId);
+            if (phone == null) {
+                getDefaultPhone().setCellInfoListRate(rateInMillis, workSource);
+            } else {
+                phone.setCellInfoListRate(rateInMillis, workSource);
+            }
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -4046,7 +4164,18 @@
         try {
             int slotId = getSlotIndexOrException(subId);
             verifyImsMmTelConfiguredOrThrow(slotId);
-            ImsManager.getInstance(mApp, slotId).addRegistrationCallbackForSubscription(c, subId);
+
+            ImsStateCallbackController controller = ImsStateCallbackController.getInstance();
+            if (controller != null) {
+                ImsManager imsManager = controller.getImsManager(subId);
+                if (imsManager != null) {
+                    imsManager.addRegistrationCallbackForSubscription(c, subId);
+                } else {
+                    throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+                }
+            } else {
+                throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
+            }
         } catch (ImsException e) {
             throw new ServiceSpecificException(e.getCode());
         } finally {
@@ -4067,14 +4196,20 @@
             throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
         }
         final long token = Binder.clearCallingIdentity();
+
         try {
-            ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
-                    .removeRegistrationCallbackForSubscription(c, subId);
-        } catch (ImsException e) {
-            Log.i(LOG_TAG, "unregisterImsRegistrationCallback: " + subId
-                    + "is inactive, ignoring unregister.");
-            // If the subscription is no longer active, just return, since the callback
-            // will already have been removed internally.
+            ImsStateCallbackController controller = ImsStateCallbackController.getInstance();
+            if (controller != null) {
+                ImsManager imsManager = controller.getImsManager(subId);
+                if (imsManager != null) {
+                    imsManager.removeRegistrationCallbackForSubscription(c, subId);
+                } else {
+                    Log.i(LOG_TAG, "unregisterImsRegistrationCallback: " + subId
+                            + "is inactive, ignoring unregister.");
+                    // If the ImsManager is not valid, just return, since the callback
+                    // will already have been removed internally.
+                }
+            }
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -5689,8 +5824,7 @@
                 // may happen if the does not support IMS.
                 return;
             }
-            mImsResolver.disableIms(slotIndex);
-            mImsResolver.enableIms(slotIndex);
+            mImsResolver.resetIms(slotIndex);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -6586,10 +6720,10 @@
             try {
                 mApp.enforceCallingOrSelfPermission(permission.READ_BASIC_PHONE_STATE,
                         functionName);
-            } catch (Exception e) {
+            } catch (SecurityException e) {
                 mApp.enforceCallingOrSelfPermission(permission.ACCESS_NETWORK_STATE, functionName);
             }
-        } catch (Exception e) {
+        } catch (SecurityException e) {
             TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
                     mApp, subId, functionName);
 
@@ -6628,17 +6762,17 @@
                 mApp.enforceCallingOrSelfPermission(
                         android.Manifest.permission.ACCESS_NETWORK_STATE,
                         functionName);
-            } catch (Exception e) {
+            } catch (SecurityException e) {
                 try {
                     mApp.enforceCallingOrSelfPermission(
                             android.Manifest.permission.READ_PHONE_STATE,
                             functionName);
-                } catch (Exception e2) {
+                } catch (SecurityException e2) {
                     mApp.enforceCallingOrSelfPermission(
                             permission.READ_BASIC_PHONE_STATE, functionName);
                 }
             }
-        } catch (Exception e) {
+        } catch (SecurityException e) {
             enforceReadPrivilegedPermission(functionName);
         }
 
@@ -6648,11 +6782,7 @@
             Phone phone = PhoneFactory.getPhone(phoneId);
             if (phone != null) {
                 boolean retVal;
-                if (phone.isUsingNewDataStack()) {
-                    retVal = phone.getDataSettingsManager().isDataEnabled();
-                } else {
-                    retVal = phone.getDataEnabledSettings().isDataEnabled();
-                }
+                retVal = phone.getDataSettingsManager().isDataEnabled();
                 if (DBG) log("isDataEnabled: " + retVal + ", subId=" + subId);
                 return retVal;
             } else {
@@ -6679,15 +6809,15 @@
                 mApp.enforceCallingOrSelfPermission(
                         android.Manifest.permission.ACCESS_NETWORK_STATE,
                         functionName);
-            } catch (Exception e) {
+            } catch (SecurityException e) {
                 mApp.enforceCallingOrSelfPermission(permission.READ_BASIC_PHONE_STATE,
                         functionName);
             }
-        } catch (Exception e) {
+        } catch (SecurityException e) {
             try {
                 mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE,
                         functionName);
-            } catch (Exception e2) {
+            } catch (SecurityException e2) {
                 TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
                         mApp, subId, functionName);
             }
@@ -6704,15 +6834,7 @@
             Phone phone = PhoneFactory.getPhone(phoneId);
             if (phone != null) {
                 boolean retVal;
-                if (phone.isUsingNewDataStack()) {
-                    retVal = phone.getDataSettingsManager().isDataEnabledForReason(reason);
-                } else {
-                    if (reason == TelephonyManager.DATA_ENABLED_REASON_USER) {
-                        retVal = phone.isUserDataEnabled();
-                    } else {
-                        retVal = phone.getDataEnabledSettings().isDataEnabledForReason(reason);
-                    }
-                }
+                retVal = phone.getDataSettingsManager().isDataEnabledForReason(reason);
                 if (DBG) log("isDataEnabledForReason: retVal=" + retVal);
                 return retVal;
             } else {
@@ -8095,8 +8217,7 @@
             }
             String vvmPackage = componentName.getPackageName();
             if (!callingPackage.equals(vvmPackage)) {
-                throw new SecurityException("Caller not current active visual voicemail package["
-                        + vvmPackage + "]");
+                throw new SecurityException("Caller not current active visual voicemail package");
             }
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -8451,12 +8572,8 @@
                 if (reason == TelephonyManager.DATA_ENABLED_REASON_CARRIER) {
                     phone.carrierActionSetMeteredApnsEnabled(enabled);
                 } else {
-                    if (phone.isUsingNewDataStack()) {
-                        phone.getDataSettingsManager().setDataEnabled(
-                                reason, enabled, callingPackage);
-                    } else {
-                        phone.getDataEnabledSettings().setDataEnabled(reason, enabled);
-                    }
+                    phone.getDataSettingsManager().setDataEnabled(
+                            reason, enabled, callingPackage);
                 }
             }
         } finally {
@@ -8667,11 +8784,11 @@
                 mApp.enforceCallingOrSelfPermission(
                         android.Manifest.permission.ACCESS_NETWORK_STATE,
                         functionName);
-            } catch (Exception e) {
+            } catch (SecurityException e) {
                 mApp.enforceCallingOrSelfPermission(
                         permission.READ_BASIC_PHONE_STATE, functionName);
             }
-        } catch (Exception e) {
+        } catch (SecurityException e) {
             TelephonyPermissions.enforceCallingOrSelfReadPhoneStatePermissionOrCarrierPrivilege(
                     mApp, subId, functionName);
         }
@@ -9233,9 +9350,11 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             for (Phone phone: PhoneFactory.getPhones()) {
+                //Note: we ignore passed in param exactMatch. We can remove it once
+                // TelephonyManager#isPotentialEmergencyNumber is removed completely
                 if (phone.getEmergencyNumberTracker() != null
                         && phone.getEmergencyNumberTracker()
-                                .isEmergencyNumber(number, exactMatch)) {
+                                .isEmergencyNumber(number)) {
                     return true;
                 }
             }
@@ -9666,15 +9785,10 @@
 
             boolean isMetered;
             boolean isDataEnabled;
-            if (phone.isUsingNewDataStack()) {
-                isMetered = phone.getDataNetworkController().getDataConfigManager()
-                        .isMeteredCapability(DataUtils.apnTypeToNetworkCapability(apnType),
-                                phone.getServiceState().getDataRoaming());
-                isDataEnabled = phone.getDataSettingsManager().isDataEnabled(apnType);
-            } else {
-                isMetered = ApnSettingUtils.isMeteredApnType(apnType, phone);
-                isDataEnabled = phone.getDataEnabledSettings().isDataEnabled(apnType);
-            }
+            isMetered = phone.getDataNetworkController().getDataConfigManager()
+                    .isMeteredCapability(DataUtils.apnTypeToNetworkCapability(apnType),
+                            phone.getServiceState().getDataRoaming());
+            isDataEnabled = phone.getDataSettingsManager().isDataEnabled(apnType);
             return !isMetered || isDataEnabled;
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -9690,13 +9804,9 @@
         try {
             Phone phone = getPhone(subId);
             if (phone == null) return true; // By default return true.
-            if (phone.isUsingNewDataStack()) {
-                return phone.getDataNetworkController().getDataConfigManager().isMeteredCapability(
-                        DataUtils.apnTypeToNetworkCapability(apnType),
-                        phone.getServiceState().getDataRoaming());
-            }
-
-            return ApnSettingUtils.isMeteredApnType(apnType, phone);
+            return phone.getDataNetworkController().getDataConfigManager().isMeteredCapability(
+                    DataUtils.apnTypeToNetworkCapability(apnType),
+                    phone.getServiceState().getDataRoaming());
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -9826,22 +9936,7 @@
             Phone phone = getPhone(subscriptionId);
             if (phone == null) return false;
 
-            switch (policy) {
-                case TelephonyManager.MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL:
-                    if (phone.isUsingNewDataStack()) {
-                        return phone.getDataSettingsManager().isDataAllowedInVoiceCall();
-                    } else {
-                        return phone.getDataEnabledSettings().isDataAllowedInVoiceCall();
-                    }
-                case TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED:
-                    if (phone.isUsingNewDataStack()) {
-                        return phone.getDataSettingsManager().isMmsAlwaysAllowed();
-                    } else {
-                        return phone.getDataEnabledSettings().isMmsAlwaysAllowed();
-                    }
-                default:
-                    throw new IllegalArgumentException(policy + " is not a valid policy");
-            }
+            return phone.getDataSettingsManager().isMobileDataPolicyEnabled(policy);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -9857,24 +9952,7 @@
             Phone phone = getPhone(subscriptionId);
             if (phone == null) return;
 
-            switch (policy) {
-                case TelephonyManager.MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL:
-                    if (phone.isUsingNewDataStack()) {
-                        phone.getDataSettingsManager().setAllowDataDuringVoiceCall(enabled);
-                    } else {
-                        phone.getDataEnabledSettings().setAllowDataDuringVoiceCall(enabled);
-                    }
-                    break;
-                case TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED:
-                    if (phone.isUsingNewDataStack()) {
-                        phone.getDataSettingsManager().setAlwaysAllowMmsData(enabled);
-                    } else {
-                        phone.getDataEnabledSettings().setAlwaysAllowMmsData(enabled);
-                    }
-                    break;
-                default:
-                    throw new IllegalArgumentException(policy + " is not a valid policy");
-            }
+            phone.getDataSettingsManager().setMobileDataPolicy(policy, enabled);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -10150,7 +10228,7 @@
         for (int i = 0; i < TelephonyManager.getDefault().getActiveModemCount(); i++) {
             Phone phone = PhoneFactory.getPhone(i);
             if (phone != null) {
-                phone.setRadioPowerForReason(enable, Phone.RADIO_POWER_REASON_THERMAL);
+                phone.setRadioPowerForReason(enable, TelephonyManager.RADIO_POWER_REASON_THERMAL);
                 isPhoneAvailable = true;
             }
         }
@@ -11129,6 +11207,62 @@
     }
 
     /**
+     * Check whether the given premium capability is available for purchase from the carrier.
+     *
+     * @param capability The premium capability to check.
+     * @param subId The subId to check the premium capability for.
+     *
+     * @return Whether the given premium capability is available to purchase.
+     */
+    @Override
+    public boolean isPremiumCapabilityAvailableForPurchase(int capability, int subId) {
+        if (!TelephonyPermissions.checkCallingOrSelfReadNonDangerousPhoneStateNoThrow(
+                mApp, "isPremiumCapabilityAvailableForPurchase")) {
+            log("Premium capability "
+                    + TelephonyManager.convertPremiumCapabilityToString(capability)
+                    + " is not available for purchase due to missing permissions.");
+            throw new SecurityException("isPremiumCapabilityAvailableForPurchase requires "
+                    + "permission READ_BASIC_PHONE_STATE.");
+        }
+
+        Phone phone = getPhone(subId);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return SliceStore.getInstance(phone)
+                    .isPremiumCapabilityAvailableForPurchase(capability);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Purchase the given premium capability from the carrier.
+     *
+     * @param capability The premium capability to purchase.
+     * @param callback The result of the purchase request.
+     * @param subId The subId to purchase the premium capability for.
+     */
+    @Override
+    public void purchasePremiumCapability(int capability, IIntegerConsumer callback, int subId) {
+        log("purchasePremiumCapability: capability="
+                + TelephonyManager.convertPremiumCapabilityToString(capability) + ", caller="
+                + getCurrentPackageName());
+
+        if (!TelephonyPermissions.checkCallingOrSelfReadNonDangerousPhoneStateNoThrow(
+                mApp, "purchasePremiumCapability")) {
+            log("purchasePremiumCapability "
+                    + TelephonyManager.convertPremiumCapabilityToString(capability)
+                    + " failed due to missing permissions.");
+            throw new SecurityException("purchasePremiumCapability requires permission "
+                    + "READ_BASIC_PHONE_STATE.");
+        }
+
+        Phone phone = getPhone(subId);
+        Pair<Integer, IIntegerConsumer> argument = new Pair<>(capability, callback);
+        sendRequestAsync(CMD_PURCHASE_PREMIUM_CAPABILITY, argument, phone, null);
+    }
+
+    /**
      * Register an IMS connection state callback
      */
     @Override
@@ -11247,12 +11381,6 @@
         }
     }
 
-    @Override
-    public boolean isUsingNewDataStack() {
-        TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "isUsingNewDataStack");
-        return getDefaultPhone().isUsingNewDataStack();
-    }
-
     /**
      * Sets the modem service class Name that Telephony will bind to.
      *
diff --git a/src/com/android/phone/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java
index 695a4a4..d0aad4a 100644
--- a/src/com/android/phone/PhoneUtils.java
+++ b/src/com/android/phone/PhoneUtils.java
@@ -469,7 +469,7 @@
 
                 // build the dialog
                 final AlertDialog newDialog =
-                        FrameworksUtils.makeAlertDialogBuilder(contextThemeWrapper)
+                        new AlertDialog.Builder(contextThemeWrapper)
                         .setMessage(text)
                         .setView(dialogView)
                         .setPositiveButton(R.string.send_button, mUSSDDialogListener)
diff --git a/src/com/android/phone/RcsProvisioningMonitor.java b/src/com/android/phone/RcsProvisioningMonitor.java
index 1ed0d72..a948d08 100644
--- a/src/com/android/phone/RcsProvisioningMonitor.java
+++ b/src/com/android/phone/RcsProvisioningMonitor.java
@@ -828,7 +828,7 @@
 
     private void onConfigReceived(int subId, byte[] config, boolean isCompressed) {
         logv("onConfigReceived, subId:" + subId + ", config:"
-                + config + ", isCompressed:" + isCompressed);
+                + Arrays.toString(config) + ", isCompressed:" + isCompressed);
         RcsProvisioningInfo info = mRcsProvisioningInfos.get(subId);
         if (info == null) {
             logd("sub[" + subId + "] has been removed");
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index 669e830..11e5b69 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -63,6 +63,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
@@ -167,12 +168,17 @@
     private static final String ALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND = "allow-package";
     private static final String DISALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND = "disallow-package";
 
+    private static final String INVALID_ENTRY_ERROR = "An emergency number (only allow '0'-'9', "
+            + "'*', '#' or '+') needs to be specified after -a in the command ";
+
+    private static final int[] ROUTING_TYPES = {EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN,
+            EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY,
+            EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL};
+
     private static final String GET_ALLOWED_NETWORK_TYPES_FOR_USER =
             "get-allowed-network-types-for-users";
     private static final String SET_ALLOWED_NETWORK_TYPES_FOR_USER =
             "set-allowed-network-types-for-users";
-    // Check if telephony new data stack is enabled.
-    private static final String GET_DATA_MODE = "get-data-mode";
     private static final String GET_IMEI = "get-imei";
     private static final String GET_SIM_SLOTS_MAPPING = "get-sim-slots-mapping";
     // Take advantage of existing methods that already contain permissions checks when possible.
@@ -329,8 +335,6 @@
             case GET_ALLOWED_NETWORK_TYPES_FOR_USER:
             case SET_ALLOWED_NETWORK_TYPES_FOR_USER:
                 return handleAllowedNetworkTypesCommand(cmd);
-            case GET_DATA_MODE:
-                return handleGetDataMode();
             case GET_IMEI:
                 return handleGetImei();
             case GET_SIM_SLOTS_MAPPING:
@@ -789,6 +793,24 @@
         return 0;
     }
 
+    private void removeEmergencyNumberTestMode(String emergencyNumber) {
+        PrintWriter errPw = getErrPrintWriter();
+        for (int routingType : ROUTING_TYPES) {
+            try {
+                mInterface.updateEmergencyNumberListTestMode(
+                        EmergencyNumberTracker.REMOVE_EMERGENCY_NUMBER_TEST_MODE,
+                        new EmergencyNumber(emergencyNumber, "", "",
+                                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                                new ArrayList<String>(),
+                                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST,
+                                routingType));
+            } catch (RemoteException ex) {
+                Log.w(LOG_TAG, "emergency-number-test-mode " + "error " + ex.getMessage());
+                errPw.println("Exception: " + ex.getMessage());
+            }
+        }
+    }
+
     private int handleEmergencyNumberTestModeCommand() {
         PrintWriter errPw = getErrPrintWriter();
         String opt = getNextOption();
@@ -796,26 +818,52 @@
             onHelpEmergencyNumber();
             return 0;
         }
-
         switch (opt) {
             case "-a": {
                 String emergencyNumberCmd = getNextArgRequired();
-                if (emergencyNumberCmd == null
-                        || !EmergencyNumber.validateEmergencyNumberAddress(emergencyNumberCmd)) {
-                    errPw.println("An emergency number (only allow '0'-'9', '*', '#' or '+') needs"
-                            + " to be specified after -a in the command ");
+                if (emergencyNumberCmd == null){
+                    errPw.println(INVALID_ENTRY_ERROR);
                     return -1;
                 }
+                String[] params = emergencyNumberCmd.split(":");
+                String emergencyNumber;
+                if (params[0] == null ||
+                        !EmergencyNumber.validateEmergencyNumberAddress(params[0])){
+                    errPw.println(INVALID_ENTRY_ERROR);
+                    return -1;
+                } else {
+                    emergencyNumber = params[0];
+                }
+                removeEmergencyNumberTestMode(emergencyNumber);
+                int emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN;
+                if (params.length > 1) {
+                    switch (params[1].toLowerCase(Locale.ROOT)) {
+                        case "emergency":
+                            emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY;
+                            break;
+                        case "normal":
+                            emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL;
+                            break;
+                        case "unknown":
+                            break;
+                        default:
+                            errPw.println("\"" + params[1] + "\" is not a valid specification for "
+                                    + "emergency call routing. Please enter either \"normal\", "
+                                    + "\"unknown\", or \"emergency\" for call routing. "
+                                    + "(-a 1234:normal)");
+                            return -1;
+                    }
+                }
                 try {
                     mInterface.updateEmergencyNumberListTestMode(
                             EmergencyNumberTracker.ADD_EMERGENCY_NUMBER_TEST_MODE,
-                            new EmergencyNumber(emergencyNumberCmd, "", "",
+                            new EmergencyNumber(emergencyNumber, "", "",
                                     EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
                                     new ArrayList<String>(),
                                     EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST,
-                                    EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN));
+                                    emergencyCallRouting));
                 } catch (RemoteException ex) {
-                    Log.w(LOG_TAG, "emergency-number-test-mode -a " + emergencyNumberCmd
+                    Log.w(LOG_TAG, "emergency-number-test-mode -a " + emergencyNumber
                             + ", error " + ex.getMessage());
                     errPw.println("Exception: " + ex.getMessage());
                     return -1;
@@ -841,20 +889,7 @@
                             + " to be specified after -r in the command ");
                     return -1;
                 }
-                try {
-                    mInterface.updateEmergencyNumberListTestMode(
-                            EmergencyNumberTracker.REMOVE_EMERGENCY_NUMBER_TEST_MODE,
-                            new EmergencyNumber(emergencyNumberCmd, "", "",
-                                    EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
-                                    new ArrayList<String>(),
-                                    EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST,
-                                    EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN));
-                } catch (RemoteException ex) {
-                    Log.w(LOG_TAG, "emergency-number-test-mode -r " + emergencyNumberCmd
-                            + ", error " + ex.getMessage());
-                    errPw.println("Exception: " + ex.getMessage());
-                    return -1;
-                }
+                removeEmergencyNumberTestMode(emergencyNumberCmd);
                 break;
             }
             case "-p": {
@@ -1646,7 +1681,7 @@
         String tag = CARRIER_CONFIG_SUBCOMMAND + " " + CC_SET_VALUES_FROM_XML + ": ";
 
         // Parse all options
-        CcOptionParseResult options = parseCcOptions(tag, false);
+        CcOptionParseResult options = parseCcOptions(tag, true);
         if (options == null) {
             return -1;
         }
@@ -2913,24 +2948,6 @@
         }
     }
 
-    private int handleGetDataMode() {
-        if (!checkShellUid()) {
-            return -1;
-        }
-
-        boolean newDataStackEnabled = false;
-        try {
-            newDataStackEnabled = mInterface.isUsingNewDataStack();
-        } catch (RemoteException e) {
-            getOutPrintWriter().println("Something went wrong. " + e);
-            return -1;
-        }
-
-        getOutPrintWriter().println("Telephony is running with the "
-                + (newDataStackEnabled ? "new" : "old") + " data stack.");
-        return 0;
-    }
-
     private int handleRadioSetModemServiceCommand() {
         PrintWriter errPw = getErrPrintWriter();
         String serviceName = null;
diff --git a/src/com/android/phone/settings/RadioInfo.java b/src/com/android/phone/settings/RadioInfo.java
index 2058d2d..288d543 100644
--- a/src/com/android/phone/settings/RadioInfo.java
+++ b/src/com/android/phone/settings/RadioInfo.java
@@ -72,6 +72,13 @@
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
 import android.telephony.data.NetworkSlicingConfig;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsManager;
+import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.ImsRcsManager;
+import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.Menu;
@@ -92,9 +99,6 @@
 import androidx.appcompat.app.AlertDialog.Builder;
 import androidx.appcompat.app.AppCompatActivity;
 
-import com.android.ims.ImsConfig;
-import com.android.ims.ImsException;
-import com.android.ims.ImsManager;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.euicc.EuiccConnector;
@@ -176,18 +180,6 @@
      */
     private static final int ALWAYS_ON_DSDS_MODE = 1;
 
-    private static final int IMS_VOLTE_PROVISIONED_CONFIG_ID =
-            ImsConfig.ConfigConstants.VLT_SETTING_ENABLED;
-
-    private static final int IMS_VT_PROVISIONED_CONFIG_ID =
-            ImsConfig.ConfigConstants.LVC_SETTING_ENABLED;
-
-    private static final int IMS_WFC_PROVISIONED_CONFIG_ID =
-            ImsConfig.ConfigConstants.VOICE_OVER_WIFI_SETTING_ENABLED;
-
-    private static final int EAB_PROVISIONED_CONFIG_ID =
-            ImsConfig.ConfigConstants.EAB_SETTING_ENABLED;
-
     //Values in must match CELL_INFO_REFRESH_RATES
     private static final String[] CELL_INFO_REFRESH_RATE_LABELS = {
             "Disabled",
@@ -291,6 +283,7 @@
     private TelephonyManager mTelephonyManager;
     private ImsManager mImsManager = null;
     private Phone mPhone = null;
+    private ProvisioningManager mProvisioningManager = null;
 
     private String mPingHostnameResultV4;
     private String mPingHostnameResultV6;
@@ -417,14 +410,15 @@
     private void updatePhoneIndex(int phoneIndex, int subId) {
         // unregister listeners on the old subId
         unregisterPhoneStateListener();
-        mTelephonyManager.setCellInfoListRate(sCellInfoListRateDisabled);
+        mTelephonyManager.setCellInfoListRate(sCellInfoListRateDisabled, mPhone.getSubId());
 
         // update the subId
         mTelephonyManager = mTelephonyManager.createForSubscriptionId(subId);
 
         // update the phoneId
-        mImsManager = ImsManager.getInstance(getApplicationContext(), phoneIndex);
         mPhone = PhoneFactory.getPhone(phoneIndex);
+        mImsManager = new ImsManager(mPhone.getContext());
+        mProvisioningManager = ProvisioningManager.createForSubscriptionId(mPhone.getSubId());
 
         updateAllFields();
     }
@@ -484,7 +478,8 @@
         mTelephonyManager = ((TelephonyManager) getSystemService(TELEPHONY_SERVICE))
                 .createForSubscriptionId(mPhone.getSubId());
 
-        mImsManager = ImsManager.getInstance(getApplicationContext(), mPhone.getPhoneId());
+        mImsManager = new ImsManager(mPhone.getContext());
+        mProvisioningManager = ProvisioningManager.createForSubscriptionId(mPhone.getSubId());
 
         sPhoneIndexLabels = getPhoneIndexLabels(mTelephonyManager);
 
@@ -552,7 +547,7 @@
         mImsWfcProvisionedSwitch = (Switch) findViewById(R.id.wfc_provisioned_switch);
         mEabProvisionedSwitch = (Switch) findViewById(R.id.eab_provisioned_switch);
 
-        if (!ImsManager.isImsSupportedOnDevice(mPhone.getContext())) {
+        if (!isImsSupportedOnDevice(mPhone.getContext())) {
             mImsVolteProvisionedSwitch.setVisibility(View.GONE);
             mImsVtProvisionedSwitch.setVisibility(View.GONE);
             mImsWfcProvisionedSwitch.setVisibility(View.GONE);
@@ -680,7 +675,8 @@
         //set selection after registering listener to force update
         mCellInfoRefreshRateSpinner.setSelection(mCellInfoRefreshRateIndex);
         // Request cell information update from RIL.
-        mTelephonyManager.setCellInfoListRate(CELL_INFO_REFRESH_RATES[mCellInfoRefreshRateIndex]);
+        mTelephonyManager.setCellInfoListRate(CELL_INFO_REFRESH_RATES[mCellInfoRefreshRateIndex],
+                mPhone.getSubId());
 
         //set selection before registering to prevent update
         mPreferredNetworkType.setSelection(mPreferredNetworkTypeResult, true);
@@ -725,7 +721,7 @@
         log("onPause: unregister phone & data intents");
 
         mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback);
-        mTelephonyManager.setCellInfoListRate(sCellInfoListRateDisabled);
+        mTelephonyManager.setCellInfoListRate(sCellInfoListRateDisabled, mPhone.getSubId());
         mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
 
     }
@@ -774,7 +770,8 @@
                 R.string.radioInfo_menu_viewFDN).setOnMenuItemClickListener(mViewFDNCallback);
         menu.add(1, MENU_ITEM_VIEW_SDN, 0,
                 R.string.radioInfo_menu_viewSDN).setOnMenuItemClickListener(mViewSDNCallback);
-        if (ImsManager.isImsSupportedOnDevice(mPhone.getContext())) {
+
+        if (isImsSupportedOnDevice(mPhone.getContext())) {
             menu.add(1, MENU_ITEM_GET_IMS_STATUS,
                     0, R.string.radioInfo_menu_getIMS).setOnMenuItemClickListener(mGetImsStatus);
         }
@@ -1502,34 +1499,38 @@
         mRadioPowerOnSwitch.setOnCheckedChangeListener(mRadioPowerOnChangeListener);
     }
 
-    void setImsVolteProvisionedState(boolean state) {
+    private void setImsVolteProvisionedState(boolean state) {
         Log.d(TAG, "setImsVolteProvisioned state: " + ((state) ? "on" : "off"));
-        setImsConfigProvisionedState(IMS_VOLTE_PROVISIONED_CONFIG_ID, state);
+        setImsConfigProvisionedState(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE, state);
     }
 
-    void setImsVtProvisionedState(boolean state) {
+    private void setImsVtProvisionedState(boolean state) {
         Log.d(TAG, "setImsVtProvisioned() state: " + ((state) ? "on" : "off"));
-        setImsConfigProvisionedState(IMS_VT_PROVISIONED_CONFIG_ID, state);
+        setImsConfigProvisionedState(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE, state);
     }
 
-    void setImsWfcProvisionedState(boolean state) {
+    private void setImsWfcProvisionedState(boolean state) {
         Log.d(TAG, "setImsWfcProvisioned() state: " + ((state) ? "on" : "off"));
-        setImsConfigProvisionedState(IMS_WFC_PROVISIONED_CONFIG_ID, state);
+        setImsConfigProvisionedState(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN, state);
     }
 
-    void setEabProvisionedState(boolean state) {
+    private void setEabProvisionedState(boolean state) {
         Log.d(TAG, "setEabProvisioned() state: " + ((state) ? "on" : "off"));
-        setImsConfigProvisionedState(EAB_PROVISIONED_CONFIG_ID, state);
+        setRcsConfigProvisionedState(ImsRcsManager.CAPABILITY_TYPE_PRESENCE_UCE,
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE, state);
     }
 
-    void setImsConfigProvisionedState(int configItem, boolean state) {
-        if (mPhone != null && mImsManager != null) {
+    private void setImsConfigProvisionedState(int capability, int tech, boolean state) {
+        if (mProvisioningManager != null) {
             mQueuedWork.execute(new Runnable() {
                 public void run() {
                     try {
-                        mImsManager.getConfigInterface().setProvisionedValue(
-                                configItem, state ? 1 : 0);
-                    } catch (ImsException e) {
+                        mProvisioningManager.setProvisioningStatusForCapability(
+                                capability, tech, state);
+                    } catch (RuntimeException e) {
                         Log.e(TAG, "setImsConfigProvisioned() exception:", e);
                     }
                 }
@@ -1537,6 +1538,71 @@
         }
     }
 
+    private void setRcsConfigProvisionedState(int capability, int tech, boolean state) {
+        if (mProvisioningManager != null) {
+            mQueuedWork.execute(new Runnable() {
+                public void run() {
+                    try {
+                        mProvisioningManager.setRcsProvisioningStatusForCapability(
+                                capability, tech, state);
+                    } catch (RuntimeException e) {
+                        Log.e(TAG, "setRcsConfigProvisioned() exception:", e);
+                    }
+                }
+            });
+        }
+    }
+
+    private boolean isImsVolteProvisioningRequired() {
+        return isImsConfigProvisioningRequired(
+                MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+    }
+
+    private boolean isImsVtProvisioningRequired() {
+        return isImsConfigProvisioningRequired(
+                MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+    }
+
+    private boolean isImsWfcProvisioningRequired() {
+        return isImsConfigProvisioningRequired(
+                MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
+    }
+
+    private boolean isEabProvisioningRequired() {
+        return isRcsConfigProvisioningRequired(
+                ImsRcsManager.CAPABILITY_TYPE_PRESENCE_UCE,
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+    }
+
+    private boolean isImsConfigProvisioningRequired(int capability, int tech) {
+        if (mProvisioningManager != null) {
+            try {
+                return mProvisioningManager.isProvisioningRequiredForCapability(
+                        capability, tech);
+            } catch (RuntimeException e) {
+                Log.e(TAG, "isImsConfigProvisioningRequired() exception:", e);
+            }
+        }
+
+        return false;
+    }
+
+    private boolean isRcsConfigProvisioningRequired(int capability, int tech) {
+        if (mProvisioningManager != null) {
+            try {
+                return mProvisioningManager.isRcsProvisioningRequiredForCapability(
+                        capability, tech);
+            } catch (RuntimeException e) {
+                Log.e(TAG, "isRcsConfigProvisioningRequired() exception:", e);
+            }
+        }
+
+        return false;
+    }
+
     OnCheckedChangeListener mRadioPowerOnChangeListener = new OnCheckedChangeListener() {
         @Override
         public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
@@ -1556,11 +1622,8 @@
     };
 
     private boolean isImsVolteProvisioned() {
-        if (mImsManager != null) {
-            return mImsManager.isVolteEnabledByPlatform()
-                && mImsManager.isVolteProvisionedOnDevice();
-        }
-        return false;
+        return getImsConfigProvisionedState(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
     }
 
     OnCheckedChangeListener mImsVolteCheckedChangeListener = new OnCheckedChangeListener() {
@@ -1571,11 +1634,8 @@
     };
 
     private boolean isImsVtProvisioned() {
-        if (mImsManager != null) {
-            return mImsManager.isVtEnabledByPlatform()
-                && mImsManager.isVtProvisionedOnDevice();
-        }
-        return false;
+        return getImsConfigProvisionedState(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
     }
 
     OnCheckedChangeListener mImsVtCheckedChangeListener = new OnCheckedChangeListener() {
@@ -1586,11 +1646,8 @@
     };
 
     private boolean isImsWfcProvisioned() {
-        if (mImsManager != null) {
-            return mImsManager.isWfcEnabledByPlatform()
-                && mImsManager.isWfcProvisionedOnDevice();
-        }
-        return false;
+        return getImsConfigProvisionedState(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
     }
 
     OnCheckedChangeListener mImsWfcCheckedChangeListener = new OnCheckedChangeListener() {
@@ -1601,7 +1658,8 @@
     };
 
     private boolean isEabProvisioned() {
-        return isFeatureProvisioned(EAB_PROVISIONED_CONFIG_ID, false);
+        return getRcsConfigProvisionedState(ImsRcsManager.CAPABILITY_TYPE_PRESENCE_UCE,
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
     }
 
     OnCheckedChangeListener mEabCheckedChangeListener = new OnCheckedChangeListener() {
@@ -1611,23 +1669,30 @@
         }
     };
 
-    private boolean isFeatureProvisioned(int featureId, boolean defaultValue) {
-        boolean provisioned = defaultValue;
-        if (mImsManager != null) {
+    private boolean getImsConfigProvisionedState(int capability, int tech) {
+        if (mProvisioningManager != null) {
             try {
-                ImsConfig imsConfig = mImsManager.getConfigInterface();
-                if (imsConfig != null) {
-                    provisioned =
-                            (imsConfig.getProvisionedValue(featureId)
-                                    == ImsConfig.FeatureValueConstants.ON);
-                }
-            } catch (ImsException ex) {
-                Log.e(TAG, "isFeatureProvisioned() exception:", ex);
+                return mProvisioningManager.getProvisioningStatusForCapability(
+                        capability, tech);
+            } catch (RuntimeException e) {
+                Log.e(TAG, "getImsConfigProvisionedState() exception:", e);
             }
         }
 
-        log("isFeatureProvisioned() featureId=" + featureId + " provisioned=" + provisioned);
-        return provisioned;
+        return false;
+    }
+
+    private boolean getRcsConfigProvisionedState(int capability, int tech) {
+        if (mProvisioningManager != null) {
+            try {
+                return mProvisioningManager.getRcsProvisioningStatusForCapability(
+                        capability, tech);
+            } catch (RuntimeException e) {
+                Log.e(TAG, "getRcsConfigProvisionedState() exception:", e);
+            }
+        }
+
+        return false;
     }
 
     private boolean isEabEnabledByPlatform() {
@@ -1646,35 +1711,56 @@
     }
 
     private void updateImsProvisionedState() {
-        if (!ImsManager.isImsSupportedOnDevice(mPhone.getContext())) {
+        if (!isImsSupportedOnDevice(mPhone.getContext())) {
             return;
         }
-        log("updateImsProvisionedState isImsVolteProvisioned()=" + isImsVolteProvisioned());
-        //delightful hack to prevent on-checked-changed calls from
-        //actually forcing the ims provisioning to its transient/current value.
+
+        updateServiceEnabledByPlatform();
+
+        updateEabProvisionedSwitch(isEabEnabledByPlatform());
+    }
+
+    private void updateVolteProvisionedSwitch(boolean isEnabledByPlatform) {
+        boolean isProvisioned = isEnabledByPlatform && isImsVolteProvisioned();
+        log("updateImsProvisionedState isProvisioned" + isProvisioned);
+
         mImsVolteProvisionedSwitch.setOnCheckedChangeListener(null);
-        mImsVolteProvisionedSwitch.setChecked(isImsVolteProvisioned());
+        mImsVolteProvisionedSwitch.setChecked(isProvisioned);
         mImsVolteProvisionedSwitch.setOnCheckedChangeListener(mImsVolteCheckedChangeListener);
         mImsVolteProvisionedSwitch.setEnabled(!IS_USER_BUILD
-                && mImsManager.isVolteEnabledByPlatform());
+                && isEnabledByPlatform && isImsVolteProvisioningRequired());
+    }
+
+    private void updateVtProvisionedSwitch(boolean isEnabledByPlatform) {
+        boolean isProvisioned = isEnabledByPlatform && isImsVtProvisioned();
+        log("updateVtProvisionedSwitch isProvisioned" + isProvisioned);
 
         mImsVtProvisionedSwitch.setOnCheckedChangeListener(null);
-        mImsVtProvisionedSwitch.setChecked(isImsVtProvisioned());
+        mImsVtProvisionedSwitch.setChecked(isProvisioned);
         mImsVtProvisionedSwitch.setOnCheckedChangeListener(mImsVtCheckedChangeListener);
         mImsVtProvisionedSwitch.setEnabled(!IS_USER_BUILD
-                && mImsManager.isVtEnabledByPlatform());
+                && isEnabledByPlatform && isImsVtProvisioningRequired());
+    }
+
+    private void updateWfcProvisionedSwitch(boolean isEnabledByPlatform) {
+        boolean isProvisioned = isEnabledByPlatform && isImsWfcProvisioned();
+        log("updateWfcProvisionedSwitch isProvisioned" + isProvisioned);
 
         mImsWfcProvisionedSwitch.setOnCheckedChangeListener(null);
-        mImsWfcProvisionedSwitch.setChecked(isImsWfcProvisioned());
+        mImsWfcProvisionedSwitch.setChecked(isProvisioned);
         mImsWfcProvisionedSwitch.setOnCheckedChangeListener(mImsWfcCheckedChangeListener);
         mImsWfcProvisionedSwitch.setEnabled(!IS_USER_BUILD
-                && mImsManager.isWfcEnabledByPlatform());
+                && isEnabledByPlatform && isImsWfcProvisioningRequired());
+    }
+
+    private void updateEabProvisionedSwitch(boolean isEnabledByPlatform) {
+        log("updateEabProvisionedSwitch isEabWfcProvisioned()=" + isEabProvisioned());
 
         mEabProvisionedSwitch.setOnCheckedChangeListener(null);
         mEabProvisionedSwitch.setChecked(isEabProvisioned());
         mEabProvisionedSwitch.setOnCheckedChangeListener(mEabCheckedChangeListener);
         mEabProvisionedSwitch.setEnabled(!IS_USER_BUILD
-                && isEabEnabledByPlatform());
+                && isEnabledByPlatform && isEabProvisioningRequired());
     }
 
     OnClickListener mDnsCheckButtonHandler = new OnClickListener() {
@@ -1795,7 +1881,7 @@
 
         public void onItemSelected(AdapterView parent, View v, int pos, long id) {
             mCellInfoRefreshRateIndex = pos;
-            mTelephonyManager.setCellInfoListRate(CELL_INFO_REFRESH_RATES[pos]);
+            mTelephonyManager.setCellInfoListRate(CELL_INFO_REFRESH_RATES[pos], mPhone.getSubId());
             updateAllCellInfo();
         }
 
@@ -1913,4 +1999,28 @@
         intent.putExtra("isDefault", isChecked);
         sendBroadcast(intent);
     }
+
+    private boolean isImsSupportedOnDevice(Context context) {
+        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS);
+    }
+
+    private void updateServiceEnabledByPlatform() {
+        ImsMmTelManager imsMmTelManager = mImsManager.getImsMmTelManager(mPhone.getSubId());
+        try {
+            imsMmTelManager.isSupported(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN, getMainExecutor(), (result) -> {
+                        updateVolteProvisionedSwitch(result);
+                    });
+            imsMmTelManager.isSupported(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
+                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN, getMainExecutor(), (result) -> {
+                        updateVtProvisionedSwitch(result);
+                    });
+            imsMmTelManager.isSupported(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                    AccessNetworkConstants.TRANSPORT_TYPE_WLAN, getMainExecutor(), (result) -> {
+                        updateWfcProvisionedSwitch(result);
+                    });
+        } catch (ImsException e) {
+            e.printStackTrace();
+        }
+    }
 }
diff --git a/src/com/android/phone/settings/VoicemailProviderSettings.java b/src/com/android/phone/settings/VoicemailProviderSettings.java
index fc2e7f8..10f0ddb 100644
--- a/src/com/android/phone/settings/VoicemailProviderSettings.java
+++ b/src/com/android/phone/settings/VoicemailProviderSettings.java
@@ -21,6 +21,8 @@
 import com.android.internal.telephony.CallForwardInfo;
 import com.android.internal.telephony.CommandsInterface;
 
+import java.util.Arrays;
+
 /**
  * Settings for a voicemail provider, including any conditional forwarding information.
  */
@@ -88,7 +90,7 @@
     @Override
     public String toString() {
         return mVoicemailNumber + ((mForwardingSettings == null) ? ""
-                : ", " + mForwardingSettings.toString());
+                : ", " + Arrays.toString(mForwardingSettings));
     }
 
     public String getVoicemailNumber() {
diff --git a/src/com/android/phone/slicestore/SliceStore.java b/src/com/android/phone/slicestore/SliceStore.java
new file mode 100644
index 0000000..aa564c8
--- /dev/null
+++ b/src/com/android/phone/slicestore/SliceStore.java
@@ -0,0 +1,378 @@
+/*
+ * 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.phone.slicestore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.ConnectivityManager;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.telephony.AnomalyReporter;
+import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
+import android.telephony.data.NetworkSliceInfo;
+import android.telephony.data.NetworkSlicingConfig;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.telephony.Phone;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * The SliceStore controls the purchase and availability of all cellular premium capabilities.
+ * Applications can check whether premium capabilities are available by calling
+ * {@link TelephonyManager#isPremiumCapabilityAvailableForPurchase(int)}. If this returns true,
+ * they can then call {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
+ * to purchase the premium capability. If all conditions are met, a notification will be displayed
+ * to the user prompting them to purchase the premium capability. If the user confirms on the
+ * notification, a (TODO: add link) WebView will open that allows the user to purchase the
+ * premium capability from the carrier. If the purchase is successful, the premium capability
+ * will be available for all applications to request through
+ * {@link ConnectivityManager#requestNetwork}.
+ */
+public class SliceStore extends Handler {
+    @NonNull private static final String TAG = "SliceStore";
+    /** Purchasing the premium capability is no longer throttled. */
+    private static final int EVENT_PURCHASE_UNTHROTTLED = 1;
+    /** Slicing config changed. */
+    private static final int EVENT_SLICING_CONFIG_CHANGED = 2;
+    /** Display booster notification. */
+    private static final int EVENT_DISPLAY_BOOSTER_NOTIFICATION = 3;
+    /** Boost was not purchased within the timeout specified by carrier configs. */
+    private static final int EVENT_PURCHASE_TIMEOUT = 4;
+
+    /** UUID to report an anomaly when a premium capability is throttled twice in a row. */
+    private static final String UUID_CAPABILITY_THROTTLED_TWICE =
+            "15574927-e2e2-4593-99d4-2f340d22b383";
+
+    /** Map of phone ID -> SliceStore. */
+    @NonNull private static final Map<Integer, SliceStore> sInstances = new HashMap<>();
+
+    @NonNull private final Phone mPhone;
+    @NonNull private final SparseBooleanArray mPurchasedCapabilities = new SparseBooleanArray();
+    @NonNull private final SparseBooleanArray mThrottledCapabilities = new SparseBooleanArray();
+    @NonNull private final SparseBooleanArray mPendingPurchaseCapabilities =
+            new SparseBooleanArray();
+    @Nullable private NetworkSlicingConfig mSlicingConfig;
+
+    /**
+     * Get the static SliceStore instance for the given phone.
+     *
+     * @param phone The phone to get the SliceStore for
+     * @return The static SliceStore instance
+     */
+    @NonNull public static synchronized SliceStore getInstance(@NonNull Phone phone) {
+        // TODO: Add listeners for multi sim setting changed (maybe carrier config changed too)
+        //  that dismiss notifications and update SliceStore instance
+        int phoneId = phone.getPhoneId();
+        if (sInstances.get(phoneId) == null) {
+            sInstances.put(phoneId, new SliceStore(phone));
+        }
+        return sInstances.get(phoneId);
+    }
+
+    private SliceStore(@NonNull Phone phone) {
+        super(Looper.myLooper());
+        mPhone = phone;
+        // TODO: Create a cached value for slicing config in DataIndication and initialize here
+        mPhone.mCi.registerForSlicingConfigChanged(this, EVENT_SLICING_CONFIG_CHANGED, null);
+    }
+
+    @Override
+    public void handleMessage(@NonNull Message msg) {
+        switch (msg.what) {
+            case EVENT_PURCHASE_UNTHROTTLED: {
+                int capability = (int) msg.obj;
+                log("EVENT_PURCHASE_UNTHROTTLED: for capability "
+                        + TelephonyManager.convertPremiumCapabilityToString(capability));
+                mThrottledCapabilities.setValueAt(capability, false);
+                break;
+            }
+            case EVENT_SLICING_CONFIG_CHANGED: {
+                AsyncResult ar = (AsyncResult) msg.obj;
+                NetworkSlicingConfig config = (NetworkSlicingConfig) ar.result;
+                log("EVENT_SLICING_CONFIG_CHANGED: from " + mSlicingConfig + " to " + config);
+                mSlicingConfig = config;
+                break;
+            }
+            case EVENT_DISPLAY_BOOSTER_NOTIFICATION: {
+                onDisplayBoosterNotification(msg.arg1, (Message) msg.obj);
+                break;
+            }
+            case EVENT_PURCHASE_TIMEOUT: {
+                int capability = msg.arg1;
+                log("EVENT_PURCHASE_TIMEOUT: for capability "
+                        + TelephonyManager.convertPremiumCapabilityToString(capability));
+                onTimeout(capability, (Message) msg.obj);
+                break;
+            }
+        }
+    }
+
+    /**
+     * Check whether the given premium capability is available for purchase from the carrier.
+     *
+     * @param capability The premium capability to check.
+     * @return Whether the given premium capability is available to purchase.
+     */
+    public boolean isPremiumCapabilityAvailableForPurchase(
+            @TelephonyManager.PremiumCapability int capability) {
+        if (!arePremiumCapabilitiesSupportedByDevice()) {
+            log("Premium capabilities unsupported by the device.");
+            return false;
+        }
+        if (!isPremiumCapabilitySupportedByCarrier(capability)) {
+            log("Premium capability "
+                    + TelephonyManager.convertPremiumCapabilityToString(capability)
+                    + " unsupported by the carrier.");
+            return false;
+        }
+        if (!arePremiumCapabilitiesEnabledByUser()) {
+            log("Premium capabilities disabled by the user.");
+            return false;
+        }
+        log("Premium capability "
+                + TelephonyManager.convertPremiumCapabilityToString(capability)
+                + " is available for purchase.");
+        return true;
+    }
+
+    /**
+     * Purchase the given premium capability from the carrier.
+     *
+     * @param capability The premium capability to purchase.
+     * @param onComplete The callback message to send when the purchase request is complete.
+     */
+    public synchronized void purchasePremiumCapability(
+            @TelephonyManager.PremiumCapability int capability, @NonNull Message onComplete) {
+        log("purchasePremiumCapability: "
+                + TelephonyManager.convertPremiumCapabilityToString(capability));
+        // Check whether the premium capability can be purchased.
+        if (!arePremiumCapabilitiesSupportedByDevice()) {
+            sendPurchaseResult(capability,
+                    TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED,
+                    onComplete);
+            return;
+        }
+        if (!isPremiumCapabilitySupportedByCarrier(capability)) {
+            sendPurchaseResult(capability,
+                    TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED,
+                    onComplete);
+            return;
+        }
+        if (!arePremiumCapabilitiesEnabledByUser()) {
+            sendPurchaseResult(capability,
+                    TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED,
+                    onComplete);
+            return;
+        }
+        if (mPurchasedCapabilities.get(capability) || isSlicingConfigActive(capability)) {
+            sendPurchaseResult(capability,
+                    TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED,
+                    onComplete);
+            return;
+        }
+        if (mThrottledCapabilities.get(capability)) {
+            sendPurchaseResult(capability,
+                    TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED,
+                    onComplete);
+            return;
+        }
+        if (mPhone.getServiceState().getDataNetworkType() != TelephonyManager.NETWORK_TYPE_NR) {
+            sendPurchaseResult(capability,
+                    TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
+                    onComplete);
+            return;
+        }
+        if (isNetworkCongested(capability)) {
+            throttleCapability(capability);
+            sendPurchaseResult(capability,
+                    TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED,
+                    onComplete);
+            return;
+        }
+        if (mPendingPurchaseCapabilities.get(capability)) {
+            sendPurchaseResult(capability,
+                    TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS,
+                    onComplete);
+            return;
+        }
+
+        // All state checks passed. Mark purchase pending and display the booster notification to
+        // prompt user purchase. Process through the handler since this method is synchronized.
+        mPendingPurchaseCapabilities.put(capability, true);
+        sendMessage(obtainMessage(EVENT_DISPLAY_BOOSTER_NOTIFICATION,
+                capability, 0 /* unused */, onComplete));
+    }
+
+    private void sendPurchaseResult(@TelephonyManager.PremiumCapability int capability,
+            @TelephonyManager.PurchasePremiumCapabilityResult int result,
+            @NonNull Message onComplete) {
+        // Send the onComplete message with the purchase result.
+        log("Purchase result for capability "
+                + TelephonyManager.convertPremiumCapabilityToString(capability)
+                + ": " + TelephonyManager.convertPurchaseResultToString(result));
+        AsyncResult.forMessage(onComplete, result, null);
+        onComplete.sendToTarget();
+    }
+
+    private void throttleCapability(@TelephonyManager.PremiumCapability int capability) {
+        // Throttle subsequent requests if necessary.
+        if (!mThrottledCapabilities.get(capability)) {
+            long throttleTime = getThrottleDuration(capability);
+            if (throttleTime > 0) {
+                log("Throttle purchase requests for capability "
+                        + TelephonyManager.convertPremiumCapabilityToString(capability) + " for "
+                        + (throttleTime / 1000) + " seconds.");
+                mThrottledCapabilities.setValueAt(capability, true);
+                sendMessageDelayed(obtainMessage(EVENT_PURCHASE_UNTHROTTLED, capability),
+                        throttleTime);
+            }
+        } else {
+            String logStr = TelephonyManager.convertPremiumCapabilityToString(capability)
+                    + " is already throttled.";
+            log(logStr);
+            AnomalyReporter.reportAnomaly(UUID.fromString(UUID_CAPABILITY_THROTTLED_TWICE), logStr);
+        }
+    }
+
+    private void onDisplayBoosterNotification(@TelephonyManager.PremiumCapability int capability,
+            @NonNull Message onComplete) {
+        long timeout = getCarrierConfigs().getLong(CarrierConfigManager
+                .KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG);
+        log("Display the booster notification for capability "
+                + TelephonyManager.convertPremiumCapabilityToString(capability) + " for "
+                + (timeout / 1000) + " seconds.");
+        sendMessageDelayed(
+                obtainMessage(EVENT_PURCHASE_TIMEOUT, capability, 0 /* unused */, onComplete),
+                timeout);
+        // TODO(b/245882092): Display notification with listener for
+        //  EVENT_USER_ACTION or EVENT_USER_CANCELED + EVENT_USER_CONFIRMED
+    }
+
+    private void closeBoosterNotification(@TelephonyManager.PremiumCapability int capability) {
+        // TODO(b/245882092): Close notification; maybe cancel purchase timeout
+    }
+
+    private void onTimeout(@TelephonyManager.PremiumCapability int capability,
+            @NonNull Message onComplete) {
+        closeBoosterNotification(capability);
+        mPendingPurchaseCapabilities.put(capability, false);
+        throttleCapability(capability);
+        sendPurchaseResult(capability, TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT,
+                onComplete);
+    }
+
+    private void onUserCanceled(@TelephonyManager.PremiumCapability int capability) {
+        // TODO(b/245882092): Process and return user canceled; throttle
+    }
+
+    private void onUserConfirmed(@TelephonyManager.PremiumCapability int capability) {
+        // TODO(b/245882092, b/245882601): Open webview listening for carrier response
+        //  --> EVENT_CARRIER_SUCCESS or EVENT_CARRIER_ERROR
+    }
+
+    private void onCarrierSuccess(@TelephonyManager.PremiumCapability int capability) {
+        // TODO(b/245882601): Process and return success.
+        //  Probably need to handle capability expiry as well
+    }
+
+    private void onCarrierError(@TelephonyManager.PremiumCapability int capability) {
+        // TODO(b/245882601): Process and return carrier error; throttle
+    }
+
+    @Nullable private PersistableBundle getCarrierConfigs() {
+        return mPhone.getContext().getSystemService(CarrierConfigManager.class)
+                .getConfigForSubId(mPhone.getSubId());
+    }
+
+    private long getThrottleDuration(@TelephonyManager.PurchasePremiumCapabilityResult int result) {
+        if (result == TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
+                || result == TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT) {
+            return getCarrierConfigs().getLong(CarrierConfigManager
+                    .KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG);
+        }
+        if (result == TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED
+                || result == TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR) {
+            return getCarrierConfigs().getLong(CarrierConfigManager
+                    .KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG);
+        }
+        return 0;
+    }
+
+    private boolean isPremiumCapabilitySupportedByCarrier(
+            @TelephonyManager.PremiumCapability int capability) {
+        int[] supportedCapabilities = getCarrierConfigs().getIntArray(
+                CarrierConfigManager.KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY);
+        if (supportedCapabilities == null) {
+            return false;
+        }
+        return Arrays.stream(supportedCapabilities)
+                .anyMatch(supportedCapability -> supportedCapability == capability);
+    }
+
+    private boolean arePremiumCapabilitiesSupportedByDevice() {
+        // TODO: Add more checks?
+        //  Maybe device resource overlay to enable/disable in addition to carrier configs
+        return (mPhone.getCachedAllowedNetworkTypesBitmask()
+                & TelephonyManager.NETWORK_TYPE_BITMASK_NR) != 0;
+    }
+
+    private boolean arePremiumCapabilitiesEnabledByUser() {
+        // TODO(b/245882396): Create and set user settings
+        return false;
+    }
+
+    private boolean isSlicingConfigActive(@TelephonyManager.PremiumCapability int capability) {
+        if (mSlicingConfig == null) {
+            return false;
+        }
+        int capabilityServiceType = getSliceServiceType(capability);
+        for (NetworkSliceInfo sliceInfo : mSlicingConfig.getSliceInfo()) {
+            // TODO: check if TrafficDescriptor has realtime capability slice
+            if (sliceInfo.getSliceServiceType() == capabilityServiceType
+                    && sliceInfo.getStatus() == NetworkSliceInfo.SLICE_STATUS_ALLOWED) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private @NetworkSliceInfo.SliceServiceType int getSliceServiceType(
+            @TelephonyManager.PremiumCapability int capability) {
+        // TODO: Implement properly -- potentially need to add new slice service types?
+        return NetworkSliceInfo.SLICE_SERVICE_TYPE_NONE;
+    }
+
+    private boolean isNetworkCongested(@TelephonyManager.PremiumCapability int capability) {
+        // TODO: Implement TS43
+        return true;
+    }
+
+    private void log(String s) {
+        Log.d(TAG + "-" + mPhone.getPhoneId(), s);
+    }
+}
diff --git a/src/com/android/services/telephony/DisconnectCauseUtil.java b/src/com/android/services/telephony/DisconnectCauseUtil.java
index 9321e1e..587ac43 100644
--- a/src/com/android/services/telephony/DisconnectCauseUtil.java
+++ b/src/com/android/services/telephony/DisconnectCauseUtil.java
@@ -25,6 +25,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.ims.ImsReasonInfo;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CallFailCause;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
@@ -101,14 +102,29 @@
     public static DisconnectCause toTelecomDisconnectCause(
             int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason,
             int phoneId, ImsReasonInfo imsReasonInfo) {
+        return toTelecomDisconnectCause(telephonyDisconnectCause, telephonyPreciseDisconnectCause,
+                reason, phoneId, imsReasonInfo, getCarrierConfigBundle(phoneId));
+    }
+
+    /**
+     * Final pre-processing method in creating a DisconnectCause.  This method should NOT be called
+     * from another class directly.  It only has private-package visibility for testing.
+     *
+     * @param carrierConfig
+     */
+    @VisibleForTesting
+    static DisconnectCause toTelecomDisconnectCause(
+            int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason,
+            int phoneId, ImsReasonInfo imsReasonInfo, PersistableBundle carrierConfig) {
         Context context = PhoneGlobals.getInstance();
+
         return new DisconnectCause(
-                toTelecomDisconnectCauseCode(telephonyDisconnectCause),
+                toTelecomDisconnectCauseCode(telephonyDisconnectCause, carrierConfig),
                 toTelecomDisconnectCauseLabel(context, telephonyDisconnectCause,
-                        telephonyPreciseDisconnectCause),
+                        telephonyPreciseDisconnectCause, carrierConfig),
                 toTelecomDisconnectCauseDescription(context, telephonyDisconnectCause, phoneId),
-                toTelecomDisconnectReason(context,telephonyDisconnectCause, reason, phoneId),
-                toTelecomDisconnectCauseTone(telephonyDisconnectCause, phoneId),
+                toTelecomDisconnectReason(context, telephonyDisconnectCause, reason, phoneId),
+                toTelecomDisconnectCauseTone(telephonyDisconnectCause, carrierConfig),
                 telephonyDisconnectCause,
                 telephonyPreciseDisconnectCause,
                 imsReasonInfo);
@@ -119,7 +135,16 @@
      * {@link android.telecom.DisconnectCause} disconnect code.
      * @return The disconnect code as defined in {@link android.telecom.DisconnectCause}.
      */
-    private static int toTelecomDisconnectCauseCode(int telephonyDisconnectCause) {
+    private static int toTelecomDisconnectCauseCode(int telephonyDisconnectCause,
+            PersistableBundle carrierConfig) {
+
+        // special case: some carriers determine what disconnect causes play the BUSY tone.
+        // hence, must adjust the disconnectCause CODE to match the tone.
+        if (doesCarrierClassifyDisconnectCauseAsBusyCause(telephonyDisconnectCause,
+                carrierConfig)) {
+            return DisconnectCause.BUSY;
+        }
+
         switch (telephonyDisconnectCause) {
             case android.telephony.DisconnectCause.LOCAL:
             //  The call was still disconnected locally, so this is not an error condition.
@@ -237,8 +262,17 @@
      * Returns a label for to the disconnect cause to be shown to the user.
      */
     private static CharSequence toTelecomDisconnectCauseLabel(
-            Context context, int telephonyDisconnectCause, int telephonyPreciseDisconnectCause) {
+            Context context, int telephonyDisconnectCause, int telephonyPreciseDisconnectCause,
+            PersistableBundle carrierConfig) {
         CharSequence label;
+
+        // special case: some carriers determine what disconnect causes play the BUSY tone.
+        // hence, must adjust the disconnectCause LABEL to match the tone.
+        if (doesCarrierClassifyDisconnectCauseAsBusyCause(telephonyDisconnectCause,
+                carrierConfig)) {
+            return context.getResources().getString(R.string.callFailed_userBusy);
+        }
+
         if (telephonyPreciseDisconnectCause != CallFailCause.NOT_VALID) {
             label = getLabelFromPreciseDisconnectCause(context, telephonyPreciseDisconnectCause,
                     telephonyDisconnectCause);
@@ -695,6 +729,10 @@
                 resourceId = R.string.incall_error_emergency_only;
                 break;
 
+            case android.telephony.DisconnectCause.ICC_ERROR:
+                resourceId = R.string.callFailed_simError;
+                break;
+
             case android.telephony.DisconnectCause.OUT_OF_SERVICE:
                 // No network connection.
                 if (ImsUtil.shouldPromoteWfc(context, phoneId)) {
@@ -840,21 +878,15 @@
     /**
      * Returns the tone to play for the disconnect cause, or UNKNOWN if none should be played.
      */
-    private static int toTelecomDisconnectCauseTone(int telephonyDisconnectCause, int phoneId) {
-        Phone phone = PhoneFactory.getPhone(phoneId);
-        PersistableBundle config;
-        if (phone != null) {
-            config = PhoneGlobals.getInstance().getCarrierConfigForSubId(phone.getSubId());
-        } else {
-            config = PhoneGlobals.getInstance().getCarrierConfig();
+    private static int toTelecomDisconnectCauseTone(int telephonyDisconnectCause,
+            PersistableBundle carrierConfig) {
+
+        // special case: some carriers determine what disconnect causes play the BUSY tone.
+        if (doesCarrierClassifyDisconnectCauseAsBusyCause(telephonyDisconnectCause,
+                carrierConfig)) {
+            return ToneGenerator.TONE_SUP_BUSY;
         }
-        int[] busyToneArray = config.getIntArray(
-                CarrierConfigManager.KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY);
-        for (int busyTone : busyToneArray) {
-            if (busyTone == telephonyDisconnectCause) {
-                return ToneGenerator.TONE_SUP_BUSY;
-            }
-        }
+
         switch (telephonyDisconnectCause) {
             case android.telephony.DisconnectCause.CONGESTION:
                 return ToneGenerator.TONE_SUP_CONGESTION;
@@ -886,4 +918,37 @@
                 return ToneGenerator.TONE_PROP_PROMPT;
         }
     }
+
+    /**
+     * Helper method that examines the carrierConfig KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY
+     * containing the DisconnectCauses that are classified as DisconnectCause.BUSY
+     * @param telephonyDisconnectCause
+     * @param carrierConfig object that holds all the carrier specific settings
+     * @return whether the cause is in the carrier config busy tone array
+     */
+    private static boolean doesCarrierClassifyDisconnectCauseAsBusyCause(
+            int telephonyDisconnectCause, PersistableBundle carrierConfig) {
+        int[] busyToneArray = carrierConfig.getIntArray(
+                CarrierConfigManager.KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY);
+        for (int busyTone : busyToneArray) {
+            if (busyTone == telephonyDisconnectCause) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static PersistableBundle getCarrierConfigBundle(int phoneId) {
+        Phone phone = PhoneFactory.getPhone(phoneId);
+        PersistableBundle config;
+
+        if (phone != null) {
+            config = PhoneGlobals.getInstance().getCarrierConfigForSubId(phone.getSubId());
+        } else {
+            config = PhoneGlobals.getInstance().getCarrierConfig();
+        }
+
+        return config;
+    }
+
 }
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index ed07726..684e03a 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -2016,32 +2016,47 @@
     }
 
     @VisibleForTesting
-    public PersistableBundle getCarrierConfig() {
+    public @NonNull PersistableBundle getCarrierConfig() {
         Phone phone = getPhone();
         if (phone == null) {
-            return null;
+            Log.w(this,
+                    "getCarrierConfig: phone is null. Returning CarrierConfigManager"
+                            + ".getDefaultConfig()");
+            return CarrierConfigManager.getDefaultConfig();
         }
-        return PhoneGlobals.getInstance().getCarrierConfigForSubId(phone.getSubId());
+
+        // potential null returned from .getCarrierConfigForSubId() and method guarantees non-null.
+        // hence, need for try/finally block
+        PersistableBundle pb = null;
+        try {
+            pb = PhoneGlobals.getInstance().getCarrierConfigForSubId(phone.getSubId());
+        } catch (Exception e) {
+            Log.e(this, e,
+                    "getCarrierConfig: caught Exception when calling "
+                            + "PhoneGlobals.getCarrierConfigForSubId(phone.getSubId()). Returning "
+                            + "CarrierConfigManager.getDefaultConfig()");
+        } finally {
+            if (pb == null) {
+                pb = CarrierConfigManager.getDefaultConfig();
+            }
+        }
+        return pb;
+    }
+
+    @VisibleForTesting
+    public boolean isRttMergeSupported(@NonNull PersistableBundle pb) {
+        return pb.getBoolean(CarrierConfigManager.KEY_ALLOW_MERGING_RTT_CALLS_BOOL);
     }
 
     private boolean canDeflectImsCalls() {
-        PersistableBundle b = getCarrierConfig();
-        // Return false if the CarrierConfig is unavailable
-        if (b != null) {
-            return b.getBoolean(
-                    CarrierConfigManager.KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL) &&
-                    isValidRingingCall();
-        }
-        return false;
+        return getCarrierConfig().getBoolean(
+                CarrierConfigManager.KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL)
+                && isValidRingingCall();
     }
 
     private boolean isCallTransferSupported() {
-        PersistableBundle b = getCarrierConfig();
-        // Return false if the CarrierConfig is unavailable
-        if (b != null) {
-            return b.getBoolean(CarrierConfigManager.KEY_CARRIER_ALLOW_TRANSFER_IMS_CALL_BOOL);
-        }
-        return false;
+        return getCarrierConfig().getBoolean(
+                CarrierConfigManager.KEY_CARRIER_ALLOW_TRANSFER_IMS_CALL_BOOL);
     }
 
     private boolean canTransfer(TelephonyConnection c) {
@@ -3038,8 +3053,6 @@
         if (isIms) {
             isVoWifiEnabled = isWfcEnabled(phone);
         }
-        boolean isRttMergeSupported = getCarrierConfig()
-                .getBoolean(CarrierConfigManager.KEY_ALLOW_MERGING_RTT_CALLS_BOOL);
         PhoneAccountHandle phoneAccountHandle = isIms ? PhoneUtils
                 .makePstnPhoneAccountHandle(phone.getDefaultPhone())
                 : PhoneUtils.makePstnPhoneAccountHandle(phone);
@@ -3077,7 +3090,7 @@
         if (mTreatAsEmergencyCall) {
             isConferenceSupported = false;
             Log.d(this, "refreshConferenceSupported = false; emergency call");
-        } else if (isRtt() && !isRttMergeSupported) {
+        } else if (isRtt() && !isRttMergeSupported(getCarrierConfig())) {
             isConferenceSupported = false;
             Log.d(this, "refreshConferenceSupported = false; rtt call");
         } else if (!isConferencingSupported || isIms && !isImsConferencingSupported) {
@@ -3134,12 +3147,9 @@
         Phone phone = getPhone();
         if (phone != null && (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA)
                 && !mOriginalConnection.isIncoming()) {
-            PersistableBundle pb = getCarrierConfig();
-            if (pb != null) {
-                showOrigDialString = pb.getBoolean(CarrierConfigManager
-                        .KEY_CONFIG_SHOW_ORIG_DIAL_STRING_FOR_CDMA_BOOL);
-                Log.d(this, "showOrigDialString: " + showOrigDialString);
-            }
+            showOrigDialString = getCarrierConfig().getBoolean(CarrierConfigManager
+                    .KEY_CONFIG_SHOW_ORIG_DIAL_STRING_FOR_CDMA_BOOL);
+            Log.d(this, "showOrigDialString: " + showOrigDialString);
         }
         return showOrigDialString;
     }
@@ -3716,8 +3726,7 @@
         if (mOriginalConnection.isIncoming()
                 && !TextUtils.isEmpty(mOriginalConnection.getAddress())
                 && mOriginalConnection.getAddress().startsWith(JAPAN_COUNTRY_CODE_WITH_PLUS_SIGN)) {
-            PersistableBundle b = getCarrierConfig();
-            return b != null && b.getBoolean(
+            return getCarrierConfig().getBoolean(
                     CarrierConfigManager.KEY_FORMAT_INCOMING_NUMBER_TO_NATIONAL_FOR_JP_BOOL);
         }
         return false;
@@ -3742,8 +3751,7 @@
      * otherwise.
      */
     private boolean supportsD2DUsingRtp() {
-        PersistableBundle b = getCarrierConfig();
-        return b != null && b.getBoolean(
+        return getCarrierConfig().getBoolean(
                 CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL);
     }
 
@@ -3751,8 +3759,7 @@
      * @return {@code true} if the carrier supports D2D using DTMF digits, {@code false} otherwise.
      */
     private boolean supportsD2DUsingDtmf() {
-        PersistableBundle b = getCarrierConfig();
-        return b != null && b.getBoolean(
+        return getCarrierConfig().getBoolean(
                 CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL);
     }
 
@@ -3761,8 +3768,7 @@
      * extensions used in D2D comms, {@code false} otherwise.
      */
     private boolean supportsSdpNegotiationOfRtpHeaderExtensions() {
-        PersistableBundle b = getCarrierConfig();
-        return b != null && b.getBoolean(
+        return getCarrierConfig().getBoolean(
                 CarrierConfigManager
                         .KEY_SUPPORTS_SDP_NEGOTIATION_OF_D2D_RTP_HEADER_EXTENSIONS_BOOL);
     }
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 071376d..e369d37 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -181,18 +181,27 @@
      */
     private static class SlotStatus {
         public int slotId;
+        public int activeSubId;
         // RAT capabilities
         public int capabilities;
         // By default, we will assume that the slots are not locked.
         public boolean isLocked = false;
         // Is the emergency number associated with the slot
         public boolean hasDialedEmergencyNumber = false;
-        //SimState
+        //SimState.
         public int simState;
 
-        public SlotStatus(int slotId, int capabilities) {
+        //helper to check if sim is really 'present' in the traditional sense.
+        // since eSIM always reports SIM_STATE_READY
+        public boolean isSubActiveAndSimPresent() {
+            return (simState != TelephonyManager.SIM_STATE_ABSENT
+                && activeSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        }
+
+        public SlotStatus(int slotId, int capabilities, int activeSubId) {
             this.slotId = slotId;
             this.capabilities = capabilities;
+            this.activeSubId = activeSubId;
         }
     }
 
@@ -221,6 +230,7 @@
         public int getPhoneId(int subId) {
             return SubscriptionManager.getPhoneId(subId);
         }
+
     };
 
     /**
@@ -229,9 +239,9 @@
     @VisibleForTesting
     public interface TelephonyManagerProxy {
         int getPhoneCount();
-        boolean hasIccCard(int slotId);
         boolean isCurrentEmergencyNumber(String number);
         Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList();
+        boolean isConcurrentCallsPossible();
     }
 
     private TelephonyManagerProxy mTelephonyManagerProxy;
@@ -250,11 +260,6 @@
         }
 
         @Override
-        public boolean hasIccCard(int slotId) {
-            return mTelephonyManager.hasIccCard(slotId);
-        }
-
-        @Override
         public boolean isCurrentEmergencyNumber(String number) {
             try {
                 return mTelephonyManager.isEmergencyNumber(number);
@@ -271,6 +276,12 @@
                 return new HashMap<>();
             }
         }
+
+        @Override
+        public boolean isConcurrentCallsPossible() {
+            // Under DSDA, need to be determined by voice capabilities
+            return mTelephonyManager.getMaxNumberOfSimultaneouslyActiveSims() > 1;
+        }
     }
 
     /**
@@ -1133,6 +1144,11 @@
                             "Invalid phone type",
                             phone.getPhoneId()));
         }
+        if (!Objects.equals(request.getAccountHandle(), accountHandle)) {
+            Log.i(this, "onCreateOutgoingConnection, update phoneAccountHandle, accountHandle = "
+                    + accountHandle);
+            connection.setPhoneAccountHandle(accountHandle);
+        }
         connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED);
         connection.setTelephonyConnectionInitializing();
         connection.setTelephonyVideoState(request.getVideoState());
@@ -1636,7 +1652,13 @@
             Bundle connExtras = c.getExtras();
             Log.i(this, "retryOutgoingOriginalConnection, redialing on Phone Id: " + newPhoneToUse);
             c.clearOriginalConnection();
-            if (phoneId != newPhoneToUse.getPhoneId()) updatePhoneAccount(c, newPhoneToUse);
+            if (phoneId != newPhoneToUse.getPhoneId()) {
+                if (!mTelephonyManagerProxy.isConcurrentCallsPossible()) {
+                    disconnectAllCallsOnOtherSubs(
+                            mPhoneUtilsProxy.makePstnPhoneAccountHandle(newPhoneToUse));
+                }
+                updatePhoneAccount(c, newPhoneToUse);
+            }
             placeOutgoingConnection(c, newPhoneToUse, videoState, connExtras);
         } else {
             // We have run out of Phones to use. Disconnect the call and destroy the connection.
@@ -1834,6 +1856,9 @@
             case CallStateException.ERROR_OTASP_PROVISIONING_IN_PROCESS:
                  cause = android.telephony.DisconnectCause.OTASP_PROVISIONING_IN_PROCESS;
                  break;
+            case CallStateException.ERROR_FDN_BLOCKED:
+                 cause = android.telephony.DisconnectCause.FDN_BLOCKED;
+                 break;
         }
         connection.setTelephonyConnectionDisconnected(
                 DisconnectCauseUtil.toTelecomDisconnectCause(cause, e.getMessage(),
@@ -2085,7 +2110,7 @@
         for (Phone phone : mPhoneFactoryProxy.getPhones()) {
             if (phone.getEmergencyNumberTracker() != null) {
                 if (phone.getEmergencyNumberTracker().isEmergencyNumber(
-                        emergencyNumberAddress, true)) {
+                        emergencyNumberAddress)) {
                     if (isAvailableForEmergencyCalls(phone)) {
                         // a)
                         if (phone.getPhoneId() == defaultVoicePhoneId) {
@@ -2161,10 +2186,11 @@
             // 5)
             // Store the RAF Capabilities for sorting later.
             int radioAccessFamily = phone.getRadioAccessFamily();
-            SlotStatus status = new SlotStatus(i, radioAccessFamily);
+            SlotStatus status = new SlotStatus(i, radioAccessFamily, phone.getSubId());
             phoneSlotStatus.add(status);
             Log.i(this, "getFirstPhoneForEmergencyCall, RAF:" +
-                    Integer.toHexString(radioAccessFamily) + " saved for Phone Id:" + i);
+                Integer.toHexString(radioAccessFamily) + " saved for Phone Id:" + i + " subId:"
+                + phone.getSubId());
             // 4)
             // Report Slot's PIN/PUK lock status for sorting later.
             int simState = mSubscriptionManagerProxy.getSimStateForSlotIdx(i);
@@ -2174,6 +2200,7 @@
                     simState == TelephonyManager.SIM_STATE_PUK_REQUIRED) {
                 status.isLocked = true;
             }
+
             // 3) Store if the Phone has the corresponding emergency number
             if (phonesWithEmergencyNumber != null) {
                 for (Phone phoneWithEmergencyNumber : phonesWithEmergencyNumber) {
@@ -2184,13 +2211,15 @@
                 }
             }
             // 6)
-            if (firstPhoneWithSim == null && mTelephonyManagerProxy.hasIccCard(i)) {
-                // The slot has a SIM card inserted, but is not in service, so keep track of this
-                // Phone. Do not return because we want to make sure that none of the other Phones
+            if (firstPhoneWithSim == null &&
+                (phone.getSubId() != SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
+                // The slot has a SIM card inserted (and an active subscription), but is not in
+                // service, so keep track of this Phone.
+                // Do not return because we want to make sure that none of the other Phones
                 // are in service (because that is always faster).
                 firstPhoneWithSim = phone;
-                Log.i(this, "getFirstPhoneForEmergencyCall, SIM card inserted, Phone Id:" +
-                        firstPhoneWithSim.getPhoneId());
+                Log.i(this, "getFirstPhoneForEmergencyCall, SIM with active sub, Phone Id:" +
+                    firstPhoneWithSim.getPhoneId());
             }
         }
         // 7)
@@ -2206,18 +2235,19 @@
             final int defaultPhoneId = mPhoneFactoryProxy.getDefaultPhone().getPhoneId();
             final Phone firstOccupiedSlot = firstPhoneWithSim;
             if (!phoneSlotStatus.isEmpty()) {
+                Log.i(this, "getFirstPhoneForEmergencyCall, list size: " + phoneSlotStatus.size()
+                    + " defaultPhoneId: " + defaultPhoneId + " firstOccupiedSlot: "
+                    + firstOccupiedSlot);
                 // Only sort if there are enough elements to do so.
                 if (phoneSlotStatus.size() > 1) {
                     Collections.sort(phoneSlotStatus, (o1, o2) -> {
-                        // Sort by non-absent SIM.
-                        if (o1.simState == TelephonyManager.SIM_STATE_ABSENT
-                                && o2.simState != TelephonyManager.SIM_STATE_ABSENT) {
-                            return -1;
-                        }
-                        if (o2.simState == TelephonyManager.SIM_STATE_ABSENT
-                                && o1.simState != TelephonyManager.SIM_STATE_ABSENT) {
+                        // Sort by non-absent SIM (SIM without active sub is considered absent).
+                        if (o1.isSubActiveAndSimPresent() && !o2.isSubActiveAndSimPresent()) {
                             return 1;
                         }
+                        if (o2.isSubActiveAndSimPresent() && !o1.isSubActiveAndSimPresent()) {
+                            return -1;
+                        }
                         // First start by seeing if either of the phone slots are locked. If they
                         // are, then sort by non-locked SIM first. If they are both locked, sort
                         // by capability instead.
@@ -2645,4 +2675,23 @@
                     }
                 });
     }
+
+    private void disconnectAllCallsOnOtherSubs (@NonNull PhoneAccountHandle handle) {
+        Collection<Connection>connections = getAllConnections();
+        connections.stream()
+                .filter(c ->
+                        (c.getState() == Connection.STATE_ACTIVE
+                                || c.getState() == Connection.STATE_HOLDING)
+                                // Include any calls not on same sub as current connection.
+                                && !Objects.equals(c.getPhoneAccountHandle(), handle))
+                .forEach(c -> {
+                    if (c instanceof TelephonyConnection) {
+                        TelephonyConnection tc = (TelephonyConnection) c;
+                        Log.i(LOG_TAG, "disconnectAllCallsOnOtherSubs: disconnect" +
+                                " %s due to redial happened on other sub.",
+                                tc.getTelecomCallId());
+                        tc.hangup(android.telephony.DisconnectCause.LOCAL);
+                    }
+                });
+    }
 }
diff --git a/src/com/android/services/telephony/rcs/RcsFeatureController.java b/src/com/android/services/telephony/rcs/RcsFeatureController.java
index 0e1cb4b..48c84b1 100644
--- a/src/com/android/services/telephony/rcs/RcsFeatureController.java
+++ b/src/com/android/services/telephony/rcs/RcsFeatureController.java
@@ -19,6 +19,7 @@
 import android.annotation.AnyThread;
 import android.content.Context;
 import android.net.Uri;
+import android.telephony.SubscriptionManager;
 import android.telephony.ims.ImsException;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
@@ -402,6 +403,17 @@
         callback.accept(mImsRcsRegistrationHelper.getImsRegistrationState());
     }
 
+    /**
+     * @return the subscription ID that is currently associated with this RCS feature.
+     */
+    public int getAssociatedSubId() {
+        RcsFeatureManager manager = getFeatureManager();
+        if (manager != null) {
+            return manager.getSubId();
+        }
+        return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    }
+
     private void updateCapabilities() {
         RcsFeatureManager manager = getFeatureManager();
         if (manager != null) {
diff --git a/src/com/android/services/telephony/rcs/TelephonyRcsService.java b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
index dfcea74..13b3a7d 100644
--- a/src/com/android/services/telephony/rcs/TelephonyRcsService.java
+++ b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
@@ -244,6 +244,22 @@
     }
 
     /**
+     * Verifies the subId supplied is the active subId for the slotId specified.
+     * If we have not processed a CARRIER_CONFIG_CHANGED indication for this subscription yet,
+     * either the subscription is not active or we have not finished setting up the feature yet.
+     * @param slotId The slotId we are verifying
+     * @param subId The subId we are verifying
+     * @return true if the subId is the active subId we are tracking for the slotId specified.
+     */
+    public boolean verifyActiveSubId(int slotId, int subId) {
+        synchronized (mLock) {
+            int currId = mSlotToAssociatedSubIds.get(slotId,
+                    SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+            return subId == currId;
+        }
+    }
+
+    /**
      * ACTION_CARRIER_CONFIG_CHANGED was received by this service for a specific slot.
      * @param slotId The slotId associated with the event.
      * @param subId The subId associated with the event. May cause the subId associated with the
diff --git a/testapps/TelephonyManagerTestApp/AndroidManifest.xml b/testapps/TelephonyManagerTestApp/AndroidManifest.xml
index 40fc549..6392c26 100644
--- a/testapps/TelephonyManagerTestApp/AndroidManifest.xml
+++ b/testapps/TelephonyManagerTestApp/AndroidManifest.xml
@@ -26,7 +26,6 @@
     <uses-permission android:name="android.permission.CALL_PRIVILEGED"/>
     <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
 
-            android.Manifest.permission.ACCESS_FINE_LOCATION
     <application android:label="TelephonyManagerTestApp">
         <activity android:name=".TelephonyManagerTestApp"
              android:label="TelephonyManagerTestApp"
diff --git a/testapps/TestSliceApp/.idea/.gitignore b/testapps/TestSliceApp/.idea/.gitignore
deleted file mode 100644
index 26d3352..0000000
--- a/testapps/TestSliceApp/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/testapps/TestSliceApp/.idea/compiler.xml b/testapps/TestSliceApp/.idea/compiler.xml
deleted file mode 100644
index fb7f4a8..0000000
--- a/testapps/TestSliceApp/.idea/compiler.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="CompilerConfiguration">
-    <bytecodeTargetLevel target="11" />
-  </component>
-</project>
\ No newline at end of file
diff --git a/testapps/TestSliceApp/.idea/gradle.xml b/testapps/TestSliceApp/.idea/gradle.xml
deleted file mode 100644
index 526b4c2..0000000
--- a/testapps/TestSliceApp/.idea/gradle.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="GradleMigrationSettings" migrationVersion="1" />
-  <component name="GradleSettings">
-    <option name="linkedExternalProjectsSettings">
-      <GradleProjectSettings>
-        <option name="testRunner" value="GRADLE" />
-        <option name="distributionType" value="DEFAULT_WRAPPED" />
-        <option name="externalProjectPath" value="$PROJECT_DIR$" />
-        <option name="modules">
-          <set>
-            <option value="$PROJECT_DIR$" />
-            <option value="$PROJECT_DIR$/app" />
-          </set>
-        </option>
-        <option name="resolveModulePerSourceSet" value="false" />
-      </GradleProjectSettings>
-    </option>
-  </component>
-</project>
\ No newline at end of file
diff --git a/testapps/TestSliceApp/.idea/misc.xml b/testapps/TestSliceApp/.idea/misc.xml
deleted file mode 100644
index a329266..0000000
--- a/testapps/TestSliceApp/.idea/misc.xml
+++ /dev/null
@@ -1,76 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="DesignSurface">
-    <option name="filePathToZoomLevelMap">
-      <map>
-        <entry key="app/src/main/res/drawable/ic_launcher_background.xml" value="0.38177083333333334" />
-        <entry key="app/src/main/res/layout/_copy.xml" value="0.365625" />
-        <entry key="app/src/main/res/layout/activity_main.xml" value="0.4891304347826087" />
-        <entry key="app/src/main/res/layout/copy.xml" value="0.37135416666666665" />
-        <entry key="app/src/main/res/layout/fragment_c_b_s.xml" value="0.473731884057971" />
-        <entry key="app/src/main/res/layout/fragment_c_b_s_copy.xml" value="0.365625" />
-        <entry key="app/src/main/res/layout/fragment_main.xml" value="0.46693840579710144" />
-        <entry key="app/src/main/res/layout/fragment_prioritize_bandwidth.xml" value="0.473731884057971" />
-        <entry key="app/src/main/res/layout/fragment_prioritize_bandwidth2.xml" value="0.365625" />
-        <entry key="app/src/main/res/layout/fragment_prioritize_latency.xml" value="0.473731884057971" />
-        <entry key="app/src/main/res/layout/fragment_prioritize_latency2.xml" value="0.365625" />
-      </map>
-    </option>
-  </component>
-  <component name="NullableNotNullManager">
-    <option name="myDefaultNullable" value="androidx.annotation.Nullable" />
-    <option name="myDefaultNotNull" value="androidx.annotation.NonNull" />
-    <option name="myNullables">
-      <value>
-        <list size="17">
-          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
-          <item index="1" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
-          <item index="2" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
-          <item index="3" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
-          <item index="4" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
-          <item index="5" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
-          <item index="6" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
-          <item index="7" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
-          <item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
-          <item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
-          <item index="10" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
-          <item index="11" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" />
-          <item index="12" class="java.lang.String" itemvalue="io.reactivex.annotations.Nullable" />
-          <item index="13" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.Nullable" />
-          <item index="14" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
-          <item index="15" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
-          <item index="16" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
-        </list>
-      </value>
-    </option>
-    <option name="myNotNulls">
-      <value>
-        <list size="17">
-          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
-          <item index="1" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
-          <item index="2" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
-          <item index="3" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
-          <item index="4" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
-          <item index="5" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
-          <item index="6" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
-          <item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
-          <item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
-          <item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
-          <item index="10" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
-          <item index="11" class="java.lang.String" itemvalue="io.reactivex.annotations.NonNull" />
-          <item index="12" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.NonNull" />
-          <item index="13" class="java.lang.String" itemvalue="lombok.NonNull" />
-          <item index="14" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
-          <item index="15" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
-          <item index="16" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
-        </list>
-      </value>
-    </option>
-  </component>
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="JDK" project-jdk-type="JavaSDK">
-    <output url="file://$PROJECT_DIR$/build/classes" />
-  </component>
-  <component name="ProjectType">
-    <option name="id" value="Android" />
-  </component>
-</project>
\ No newline at end of file
diff --git a/testapps/TestSliceApp/.idea/vcs.xml b/testapps/TestSliceApp/.idea/vcs.xml
deleted file mode 100644
index 498ba99..0000000
--- a/testapps/TestSliceApp/.idea/vcs.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="IssueNavigationConfiguration">
-    <option name="links">
-      <list>
-        <IssueNavigationLink>
-          <option name="issueRegexp" value="\bb/(\d+)(#\w+)?\b" />
-          <option name="linkRegexp" value="https://buganizer.corp.google.com/issues/$1$2" />
-        </IssueNavigationLink>
-        <IssueNavigationLink>
-          <option name="issueRegexp" value="\b(?:BUG=|FIXED=)(\d+)\b" />
-          <option name="linkRegexp" value="https://buganizer.corp.google.com/issues/$1" />
-        </IssueNavigationLink>
-        <IssueNavigationLink>
-          <option name="issueRegexp" value="\b(?:cl/|cr/|OCL=|DIFFBASE=|ROLLBACK_OF=)(\d+)\b" />
-          <option name="linkRegexp" value="https://critique.corp.google.com/$1" />
-        </IssueNavigationLink>
-        <IssueNavigationLink>
-          <option name="issueRegexp" value="\bomg/(\d+)\b" />
-          <option name="linkRegexp" value="https://omg.corp.google.com/$1" />
-        </IssueNavigationLink>
-        <IssueNavigationLink>
-          <option name="issueRegexp" value="\b(?:go/|goto/)([^,.&lt;&gt;()&quot;\s]+(?:[.,][^,.&lt;&gt;()&quot;\s]+)*)" />
-          <option name="linkRegexp" value="https://goto.google.com/$1" />
-        </IssueNavigationLink>
-        <IssueNavigationLink>
-          <option name="issueRegexp" value="\bcs/([^\s]+[\w$])" />
-          <option name="linkRegexp" value="https://cs.corp.google.com/search/?q=$1" />
-        </IssueNavigationLink>
-        <IssueNavigationLink>
-          <option name="issueRegexp" value="(LINT\.IfChange)|(LINT\.ThenChange)" />
-          <option name="linkRegexp" value="https://goto.google.com/ifthisthenthatlint" />
-        </IssueNavigationLink>
-      </list>
-    </option>
-  </component>
-  <component name="VcsDirectoryMappings">
-    <mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
-  </component>
-</project>
\ No newline at end of file
diff --git a/testapps/TestSliceApp/app/src/main/AndroidManifest.xml b/testapps/TestSliceApp/app/src/main/AndroidManifest.xml
index d28bbb0..a34c254 100644
--- a/testapps/TestSliceApp/app/src/main/AndroidManifest.xml
+++ b/testapps/TestSliceApp/app/src/main/AndroidManifest.xml
@@ -3,6 +3,8 @@
     package="com.google.android.sample.testsliceapp">
 
   <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+  <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+  <uses-permission android:name="android.permission.INTERNET" />
   <application
       android:allowBackup="true"
       android:icon="@mipmap/ic_launcher"
@@ -10,7 +12,8 @@
       android:roundIcon="@mipmap/ic_launcher_round"
       android:supportsRtl="true"
       android:theme="@style/Theme.AppCompat"
-      android:versionCode="34">
+      android:versionCode="34"
+      android:usesCleartextTraffic="true">
     <activity
         android:name=".MainActivity"
         android:exported="true">
@@ -29,4 +32,4 @@
           android:value="true" />
     </service>
   </application>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/CBS.java b/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/CBS.java
index a555ce6..c85f830 100644
--- a/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/CBS.java
+++ b/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/CBS.java
@@ -20,7 +20,10 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
+import android.net.TelephonyNetworkSpecifier;
 import android.os.Bundle;
+import android.telephony.SubscriptionManager;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -62,6 +65,7 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        mConnectivityManager = getContext().getSystemService(ConnectivityManager.class);
     }
 
     @Override
@@ -80,23 +84,33 @@
         mRelease.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View view) {
-                mConnectivityManager.unregisterNetworkCallback(
+                try {
+                    mConnectivityManager.unregisterNetworkCallback(
                         mProfileCheckNetworkCallback);
+                } catch (Exception e) {
+                    Log.d("SliceTest", "Exception: " + e);
+                }
             }
         });
         mRequest = view.findViewById(R.id.requestcbs);
         mRequest.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View view) {
-                NetworkCallback mProfileCheckNetworkCallback = new NetworkCallback() {
+                mProfileCheckNetworkCallback = new NetworkCallback() {
                     @Override
                     public void onAvailable(final Network network) {
                         mNetwork = network;
+                        Log.d("CBS", "onAvailable + " + network);
                     }
                 };
                 NetworkRequest.Builder builder = new NetworkRequest.Builder();
                 builder.addCapability(NetworkCapabilities.NET_CAPABILITY_CBS);
+                builder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+                int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+                builder.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+                        .setSubscriptionId(subId).build());
                 mConnectivityManager.requestNetwork(builder.build(), mProfileCheckNetworkCallback);
+                Log.d("CBS", "onClick + " + builder.build());
             }
         });
         mPing = view.findViewById(R.id.pingcbs);
@@ -106,8 +120,9 @@
                 if (mNetwork != null) {
                     //mNetwork.
                     try {
-                        new RequestTask().ping(mNetwork);
+                        new RequestTask().execute(mNetwork);
                     } catch (Exception e) {
+                        Log.d("SliceTest", "Exception: " + e);
                     }
                 }
             }
diff --git a/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/PrioritizeBandwidth.java b/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/PrioritizeBandwidth.java
index d997178..6812ddc 100644
--- a/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/PrioritizeBandwidth.java
+++ b/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/PrioritizeBandwidth.java
@@ -21,6 +21,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.os.Bundle;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -73,6 +74,7 @@
                 new NetworkCallback() {
             @Override
             public void onAvailable(final Network network) {
+                Log.d("SliceTest", "onAvailable: " + network);
                 mNetwork = network;
             }
         };
@@ -80,23 +82,30 @@
         mRelease.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View view) {
-                mConnectivityManager.unregisterNetworkCallback(mProfileCheckNetworkCallback);
+                try {
+                    mConnectivityManager.unregisterNetworkCallback(
+                            mProfileCheckNetworkCallback);
+                } catch (Exception e) {
+                    Log.d("SliceTest", "Exception: " + e);
+                }
             }
         });
         mRequest = view.findViewById(R.id.requestbw);
         mRequest.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View view) {
-                NetworkCallback mProfileCheckNetworkCallback =
+                mProfileCheckNetworkCallback =
                         new NetworkCallback() {
                     @Override
                     public void onAvailable(final Network network) {
+                        Log.d("PrioritizeBandwidth", "onAvailable + " + network);
                         mNetwork = network;
                     }
                 };
                 NetworkRequest.Builder builder = new NetworkRequest.Builder();
                 builder.addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH);
                 mConnectivityManager.requestNetwork(builder.build(), mProfileCheckNetworkCallback);
+                Log.d("PrioritizeBandwidth", "onClick + " + builder.build());
             }
         });
         mPing = view.findViewById(R.id.pingbw);
@@ -104,10 +113,10 @@
             @Override
             public void onClick(View view) {
                 if (mNetwork != null) {
-                    //mNetwork.
                     try {
-                        new RequestTask().ping(mNetwork);
+                        new RequestTask().execute(mNetwork);
                     } catch (Exception e) {
+                        Log.d("SliceTest", "Exception: " + e);
                     }
                 }
             }
diff --git a/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/PrioritizeLatency.java b/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/PrioritizeLatency.java
index b45362c..45ea666 100644
--- a/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/PrioritizeLatency.java
+++ b/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/PrioritizeLatency.java
@@ -21,6 +21,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.os.Bundle;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -61,6 +62,7 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        mConnectivityManager = getContext().getSystemService(ConnectivityManager.class);
     }
 
     @Override
@@ -79,22 +81,29 @@
         mRelease.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View view) {
-                mConnectivityManager.unregisterNetworkCallback(mProfileCheckNetworkCallback);
+                try {
+                    mConnectivityManager.unregisterNetworkCallback(
+                            mProfileCheckNetworkCallback);
+                } catch (Exception e) {
+                    Log.d("SliceTest", "Exception: " + e);
+                }
             }
         });
         mRequest = view.findViewById(R.id.requestlatency);
         mRequest.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View view) {
-                NetworkCallback mProfileCheckNetworkCallback = new NetworkCallback() {
+                mProfileCheckNetworkCallback = new NetworkCallback() {
                     @Override
                     public void onAvailable(final Network network) {
+                        Log.d("PrioritizeLatency", "onAvailable + " + network);
                         mNetwork = network;
                     }
                 };
                 NetworkRequest.Builder builder = new NetworkRequest.Builder();
                 builder.addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY);
                 mConnectivityManager.requestNetwork(builder.build(), mProfileCheckNetworkCallback);
+                Log.d("PrioritizeLatency", "onClick + " + builder.build());
             }
         });
         mPing = view.findViewById(R.id.pinglatency);
@@ -106,6 +115,7 @@
                     try {
                         new RequestTask().ping(mNetwork);
                     } catch (Exception e) {
+                        Log.d("SliceTest", "Exception: " + e);
                     }
                 }
             }
diff --git a/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/RequestTask.java b/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/RequestTask.java
index b12939e..3849860 100644
--- a/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/RequestTask.java
+++ b/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/RequestTask.java
@@ -16,6 +16,8 @@
 package com.google.android.sample.testsliceapp;
 
 import android.net.Network;
+import android.os.AsyncTask;
+import android.util.Log;
 
 import java.io.BufferedInputStream;
 import java.io.IOException;
@@ -23,7 +25,11 @@
 import java.net.HttpURLConnection;
 import java.net.URL;
 
-class RequestTask{
+class RequestTask extends AsyncTask<Network, Integer, Integer> {
+    protected Integer doInBackground(Network... network) {
+        ping(network[0]);
+        return 0;
+    }
     String ping(Network network) {
         URL url = null;
         try {
@@ -32,8 +38,12 @@
         }
         if (url != null) {
             try {
-                return httpGet(network, url);
+                Log.d("SliceTest", "ping " + url);
+                String result = httpGet(network, url);
+                Log.d("SliceTest", "result " + result);
+                return result;
             } catch (Exception e) {
+                Log.d("SliceTest", "exception: " + e);
             }
         }
         return "";
@@ -47,6 +57,7 @@
         HttpURLConnection connection = (HttpURLConnection) network.openConnection(httpUrl);
         try {
             InputStream inputStream = connection.getInputStream();
+            Log.d("httpGet", "httpUrl + " + httpUrl);
             return new BufferedInputStream(inputStream).toString();
         } finally {
             connection.disconnect();
diff --git a/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/TestCarrierService.java b/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/TestCarrierService.java
index b1d019e..daa1d17 100644
--- a/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/TestCarrierService.java
+++ b/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/TestCarrierService.java
@@ -21,6 +21,7 @@
 import android.service.carrier.CarrierService;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
+import android.util.Log;
 
 /**
  * Carrier Service that sets the carrier config upon being bound by the system. Requires UICC
@@ -32,11 +33,13 @@
         CarrierConfigManager cfgMgr =
                 (CarrierConfigManager) getSystemService(Context.CARRIER_CONFIG_SERVICE);
         cfgMgr.notifyConfigChangedForSubId(SubscriptionManager.getDefaultSubscriptionId());
+        Log.d("TestCarrierService", "onCreate + ");
     }
 
     @Override
     public PersistableBundle onLoadConfig(CarrierIdentifier carrierIdentifier) {
         PersistableBundle config = new PersistableBundle();
+        Log.d("TestCarrierService", "onLoadConfig + ");
         return config;
     }
 }
diff --git a/testapps/TestSliceApp/app/src/main/res/layout/fragment_c_b_s.xml b/testapps/TestSliceApp/app/src/main/res/layout/fragment_c_b_s.xml
index ac2ef9d..5305b53 100644
--- a/testapps/TestSliceApp/app/src/main/res/layout/fragment_c_b_s.xml
+++ b/testapps/TestSliceApp/app/src/main/res/layout/fragment_c_b_s.xml
@@ -8,9 +8,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:id="@+id/frameLayoutCBS">
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
+<androidx.constraintlayout.widget.ConstraintLayout
     android:id="@+id/frameLayout3"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
@@ -26,23 +24,26 @@
       android:id="@+id/requestcbs"
       android:layout_width="186dp"
       android:layout_height="57dp"
+      android:layout_marginTop="164dp"
       android:text="Request Network"
-      tools:layout_editor_absoluteX="120dp"
-      tools:layout_editor_absoluteY="154dp" />
+      app:layout_constraintTop_toTopOf="parent"
+      tools:layout_editor_absoluteX="112dp" />
   <Button
       android:id="@+id/releasecbs"
       android:layout_width="187dp"
       android:layout_height="61dp"
+      android:layout_marginTop="124dp"
       android:text="Release Network"
-      tools:layout_editor_absoluteX="119dp"
-      tools:layout_editor_absoluteY="273dp" />
+      app:layout_constraintTop_toBottomOf="@+id/requestcbs"
+      tools:layout_editor_absoluteX="119dp" />
   <Button
       android:id="@+id/pingcbs"
       android:layout_width="186dp"
       android:layout_height="55dp"
       android:text="Ping"
-      tools:layout_editor_absoluteX="120dp"
-      tools:layout_editor_absoluteY="379dp" />
+      app:layout_constraintBottom_toBottomOf="parent"
+      app:layout_constraintTop_toBottomOf="@+id/releasecbs"
+      tools:layout_editor_absoluteX="120dp" />
 </androidx.constraintlayout.widget.ConstraintLayout>
 </FrameLayout>
 </RelativeLayout>
\ No newline at end of file
diff --git a/testapps/TestSliceApp/app/src/main/res/layout/fragment_prioritize_latency.xml b/testapps/TestSliceApp/app/src/main/res/layout/fragment_prioritize_latency.xml
index 9527d69..b040995 100644
--- a/testapps/TestSliceApp/app/src/main/res/layout/fragment_prioritize_latency.xml
+++ b/testapps/TestSliceApp/app/src/main/res/layout/fragment_prioritize_latency.xml
@@ -8,51 +8,52 @@
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:id="@+id/frameLayoutLatency">
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:id="@+id/frameLayout"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    tools:context=".PrioritizeLatency" >
-  <Button
-      android:id="@+id/requestlatency"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:text="RequestNetwork"
-      app:layout_constraintBottom_toTopOf="@+id/button6"
-      app:layout_constraintEnd_toEndOf="parent"
-      app:layout_constraintHorizontal_bias="0.461"
-      app:layout_constraintStart_toStartOf="parent"
-      app:layout_constraintTop_toTopOf="parent"
-      app:layout_constraintVertical_bias="0.717" />
-  <Button
-      android:id="@+id/releaselatency"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:layout_marginBottom="76dp"
-      android:text="Release Network"
-      app:layout_constraintBottom_toTopOf="@+id/button7"
-      app:layout_constraintEnd_toEndOf="parent"
-      app:layout_constraintHorizontal_bias="0.478"
-      app:layout_constraintStart_toStartOf="parent" />
-  <Button
-      android:id="@+id/pinglatency"
-      android:layout_width="182dp"
-      android:layout_height="42dp"
-      android:layout_marginBottom="308dp"
-      android:text="Ping"
-      app:layout_constraintBottom_toBottomOf="parent"
-      app:layout_constraintEnd_toEndOf="parent"
-      app:layout_constraintHorizontal_bias="0.471"
-      app:layout_constraintStart_toStartOf="parent" />
-  <TextView
-      android:id="@+id/textView"
-      android:layout_width="371dp"
-      android:layout_height="52dp"
-      android:text="Prioritize Latency"
-      tools:layout_editor_absoluteX="21dp"
-      tools:layout_editor_absoluteY="1dp" />
-</androidx.constraintlayout.widget.ConstraintLayout>
-    </FrameLayout>
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/frameLayout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        tools:context=".PrioritizeLatency">
+      <Button
+          android:id="@+id/requestlatency"
+          android:layout_width="183dp"
+          android:layout_height="50dp"
+          android:layout_marginTop="176dp"
+          android:text="RequestNetwork"
+          app:layout_constraintBottom_toTopOf="@+id/button6"
+          app:layout_constraintEnd_toEndOf="parent"
+          app:layout_constraintHorizontal_bias="0.495"
+          app:layout_constraintStart_toStartOf="parent"
+          app:layout_constraintTop_toTopOf="parent"
+          app:layout_constraintVertical_bias="0.717" />
+      <Button
+          android:id="@+id/releaselatency"
+          android:layout_width="183dp"
+          android:layout_height="50dp"
+          android:layout_marginTop="84dp"
+          android:text="ReleaseNetwork"
+          app:layout_constraintBottom_toTopOf="@+id/button6"
+          app:layout_constraintEnd_toEndOf="parent"
+          app:layout_constraintStart_toStartOf="parent"
+          app:layout_constraintTop_toBottomOf="@+id/requestlatency"
+          app:layout_constraintVertical_bias="0.717" />
+      <Button
+          android:id="@+id/pinglatency"
+          android:layout_width="182dp"
+          android:layout_height="42dp"
+          android:layout_marginBottom="92dp"
+          android:text="Ping"
+          app:layout_constraintBottom_toBottomOf="parent"
+          app:layout_constraintEnd_toEndOf="parent"
+          app:layout_constraintHorizontal_bias="0.493"
+          app:layout_constraintStart_toStartOf="parent"
+          app:layout_constraintTop_toBottomOf="@+id/releaselatency" />
+      <TextView
+          android:id="@+id/textView"
+          android:layout_width="371dp"
+          android:layout_height="52dp"
+          android:text="Prioritize Latency"
+          tools:layout_editor_absoluteX="16dp"
+          tools:layout_editor_absoluteY="16dp" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+  </FrameLayout>
     </RelativeLayout>
\ No newline at end of file
diff --git a/tests/src/com/android/phone/ImsProvisioningControllerTest.java b/tests/src/com/android/phone/ImsProvisioningControllerTest.java
index 2094e20..db83cca 100644
--- a/tests/src/com/android/phone/ImsProvisioningControllerTest.java
+++ b/tests/src/com/android/phone/ImsProvisioningControllerTest.java
@@ -63,6 +63,7 @@
 import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.aidl.IFeatureProvisioningCallback;
 import android.telephony.ims.aidl.IImsConfig;
+import android.telephony.ims.aidl.IImsConfigCallback;
 import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities;
 import android.telephony.ims.feature.RcsFeature.RcsImsCapabilities;
 import android.telephony.ims.stub.ImsConfigImplBase;
@@ -159,6 +160,9 @@
     @Mock
     IFeatureProvisioningCallback mIFeatureProvisioningCallback1;
 
+    @Captor
+    ArgumentCaptor<IImsConfigCallback> mIImsConfigCallback;
+
     @Mock
     IBinder mIbinder0;
     @Mock
@@ -347,6 +351,8 @@
         mSubChangedListener.onSubscriptionsChanged();
         processAllMessages();
 
+        verify(mImsConfig, times(1)).addConfigCallback((IImsConfigCallback) any());
+
         int[] keys = {
                 ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE,
                 ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS,
@@ -390,6 +396,8 @@
         mRcsConnectorListener0.getValue().connectionReady(mRcsFeatureManager, mSubId0);
         processAllMessages();
 
+        verify(mImsConfig, times(1)).addConfigCallback((IImsConfigCallback) any());
+
         // verify # of read data times from storage : # of Rcs storage length
         verify(mImsProvisioningLoader, times(1))
                 .getProvisioningStatus(eq(mSubId0), eq(FEATURE_RCS), anyInt(), anyInt());
@@ -1736,6 +1744,110 @@
         verifyNoMoreInteractions(mImsProvisioningLoader);
     }
 
+    @Test
+    @SmallTest
+    public void changedProvisioningValue_withMmTel() throws Exception {
+        createImsProvisioningController();
+
+        // provisioning required capability
+        // voice, all tech
+        // video, all tech
+        setCarrierConfig(mSubId0, CarrierConfigManager.Ims.KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY,
+                RADIO_TECHS);
+        setCarrierConfig(mSubId0, CarrierConfigManager.Ims.KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY,
+                RADIO_TECHS);
+
+        try {
+            mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                    mSubId0, mIFeatureProvisioningCallback0);
+        } catch (Exception e) {
+            throw new AssertionError("not expected exception", e);
+        }
+
+        mMmTelConnectorListener0.getValue().connectionReady(mImsManager, mSubId0);
+
+        // clear interactions
+        clearInvocations(mIFeatureProvisioningCallback0);
+        clearInvocations(mImsConfig);
+        clearInvocations(mImsProvisioningLoader);
+
+        // MmTel valid
+        int[] keys = {
+                ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS,
+                ProvisioningManager.KEY_VT_PROVISIONING_STATUS,
+                ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE
+        };
+        int[] capas = {
+                MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
+                MmTelCapabilities.CAPABILITY_TYPE_VOICE
+        };
+        int[] techs = {
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
+                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN
+        };
+
+        for (int index = 0; index < keys.length; index++) {
+            mIImsConfigCallback.getValue().onIntConfigChanged(keys[index],
+                    PROVISIONING_VALUE_DISABLED);
+            processAllMessages();
+
+            // verify # of read data times from storage : # of MmTel storage length
+            verify(mImsProvisioningLoader, times(1))
+                    .setProvisioningStatus(eq(mSubId0), eq(FEATURE_MMTEL), eq(capas[index]),
+                            eq(techs[index]), eq(false));
+
+            verify(mIFeatureProvisioningCallback0, times(1))
+                    .onFeatureProvisioningChanged(eq(capas[index]), eq(techs[index]), eq(false));
+        }
+
+        verifyNoMoreInteractions(mImsProvisioningLoader);
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback0);
+        verifyNoMoreInteractions(mImsConfig);
+    }
+
+    @Test
+    @SmallTest
+    public void changedProvisioningValue_withRcs() throws Exception {
+        createImsProvisioningController();
+
+        // provisioning required capability : PRESENCE, tech : all
+        setCarrierConfig(mSubId0,
+                CarrierConfigManager.Ims.KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY, RADIO_TECHS);
+
+        try {
+            mTestImsProvisioningController.addFeatureProvisioningChangedCallback(
+                    mSubId0, mIFeatureProvisioningCallback0);
+        } catch (Exception e) {
+            throw new AssertionError("not expected exception", e);
+        }
+
+        mRcsConnectorListener0.getValue().connectionReady(mRcsFeatureManager, mSubId0);
+
+        // clear interactions
+        clearInvocations(mIFeatureProvisioningCallback0);
+        clearInvocations(mImsConfig);
+        clearInvocations(mImsProvisioningLoader);
+
+        mIImsConfigCallback.getValue().onIntConfigChanged(KEY_EAB_PROVISIONING_STATUS,
+                PROVISIONING_VALUE_DISABLED);
+        processAllMessages();
+
+        // verify # of read data times from storage : # of MmTel storage length
+        verify(mImsProvisioningLoader, times(RADIO_TECHS.length))
+                .setProvisioningStatus(eq(mSubId0), eq(FEATURE_RCS),
+                        eq(CAPABILITY_TYPE_PRESENCE_UCE), anyInt(), eq(false));
+
+        verify(mIFeatureProvisioningCallback0, times(RADIO_TECHS.length))
+                .onRcsFeatureProvisioningChanged(eq(CAPABILITY_TYPE_PRESENCE_UCE), anyInt(),
+                        eq(false));
+
+        verifyNoMoreInteractions(mImsProvisioningLoader);
+        verifyNoMoreInteractions(mIFeatureProvisioningCallback0);
+        verifyNoMoreInteractions(mImsConfig);
+    }
+
     private void createImsProvisioningController() throws Exception {
         if (Looper.myLooper() == null) {
             Looper.prepare();
@@ -1755,6 +1867,9 @@
                 .create(any(), eq(1), mRcsConnectorListener1.capture(), any(), any()))
                 .thenReturn(mRcsFeatureConnector1);
 
+        doNothing().when(mImsConfig).addConfigCallback(mIImsConfigCallback.capture());
+        doNothing().when(mImsConfig).removeConfigCallback(any());
+
         when(mImsConfig.getConfigInt(anyInt()))
                 .thenAnswer(invocation -> {
                     int i = (Integer) (invocation.getArguments()[0]);
diff --git a/tests/src/com/android/phone/ImsStateCallbackControllerTest.java b/tests/src/com/android/phone/ImsStateCallbackControllerTest.java
index cb4321c..60374bc 100644
--- a/tests/src/com/android/phone/ImsStateCallbackControllerTest.java
+++ b/tests/src/com/android/phone/ImsStateCallbackControllerTest.java
@@ -28,6 +28,8 @@
 import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_NOT_READY;
 
 import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.Matchers.any;
@@ -874,6 +876,36 @@
         assertFalse(mImsStateCallbackController.isRegistered(mCallback1));
     }
 
+    @Test
+    @SmallTest
+    public void testImsManagerInstance() throws Exception {
+        createController(1);
+
+        // MmTelConnection not ready
+        // check ImsManager instance
+        ImsManager imsManager = mImsStateCallbackController.getImsManager(SLOT_0_SUB_ID);
+        assertNull(imsManager);
+
+        // MmTelConnection ready
+        mMmTelConnectorListenerSlot0.getValue()
+                .connectionReady(mMmTelFeatureManager, SLOT_0_SUB_ID);
+        processAllMessages();
+
+        // check ImsManager instance
+        imsManager = mImsStateCallbackController.getImsManager(SLOT_0_SUB_ID);
+        assertNotNull(imsManager);
+
+        // MmTelConnection unavailable
+        mMmTelConnectorListenerSlot0.getValue()
+                .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+        processAllMessages();
+
+        // MmTelConnection unavailable
+        // check ImsManager instance
+        imsManager = mImsStateCallbackController.getImsManager(SLOT_0_SUB_ID);
+        assertNull(imsManager);
+    }
+
     private void createController(int slotCount) throws Exception {
         if (Looper.myLooper() == null) {
             Looper.prepare();
diff --git a/tests/src/com/android/phone/NotificationMgrTest.java b/tests/src/com/android/phone/NotificationMgrTest.java
new file mode 100644
index 0000000..a6ee276
--- /dev/null
+++ b/tests/src/com/android/phone/NotificationMgrTest.java
@@ -0,0 +1,661 @@
+/*
+ * 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.phone;
+
+import static android.telephony.RadioAccessFamily.RAF_1xRTT;
+import static android.telephony.RadioAccessFamily.RAF_EDGE;
+import static android.telephony.RadioAccessFamily.RAF_EHRPD;
+import static android.telephony.RadioAccessFamily.RAF_EVDO_0;
+import static android.telephony.RadioAccessFamily.RAF_EVDO_A;
+import static android.telephony.RadioAccessFamily.RAF_EVDO_B;
+import static android.telephony.RadioAccessFamily.RAF_GPRS;
+import static android.telephony.RadioAccessFamily.RAF_GSM;
+import static android.telephony.RadioAccessFamily.RAF_HSDPA;
+import static android.telephony.RadioAccessFamily.RAF_HSPA;
+import static android.telephony.RadioAccessFamily.RAF_HSPAP;
+import static android.telephony.RadioAccessFamily.RAF_HSUPA;
+import static android.telephony.RadioAccessFamily.RAF_IS95A;
+import static android.telephony.RadioAccessFamily.RAF_IS95B;
+import static android.telephony.RadioAccessFamily.RAF_LTE;
+import static android.telephony.RadioAccessFamily.RAF_LTE_CA;
+import static android.telephony.RadioAccessFamily.RAF_TD_SCDMA;
+import static android.telephony.RadioAccessFamily.RAF_UMTS;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+import static com.android.phone.NotificationMgr.DATA_ROAMING_NOTIFICATION;
+import static com.android.phone.NotificationMgr.LIMITED_SIM_FUNCTION_NOTIFICATION;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.telecom.TelecomManager;
+import android.telephony.CarrierConfigManager;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.util.NotificationChannelController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.reflect.Field;
+
+/**
+ * Unit Test for NotificationMgr
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationMgrTest {
+
+    private static final int TEST_SUB_ID = 1;
+    private static final long SERIAL_NUMBER_OF_USER = 1234567L;
+    private static final String TEST_LABEL_CF = "test_call_forwarding";
+    private static final String TEST_SUB_INFO_DISPLAY_NAME = "display_name";
+    private static final String TEST_PACKAGE_NAME = "com.android.phone";
+    private static final String TEST_SELECTED_NETWORK_OPERATOR_NAME = "TheOperator";
+    private static final String MOBILE_NETWORK_SELECTION_PACKAGE = "com.android.phone";
+    private static final String MOBILE_NETWORK_SELECTION_CLASS = ".testClass";
+    private static final String CARRIER_NAME = "CoolCarrier";
+
+    @Mock PhoneGlobals mApp;
+    @Mock StatusBarManager mStatusBarManager;
+    @Mock UserManager mUserManager;
+    @Mock SubscriptionManager mSubscriptionManager;
+    @Mock TelecomManager mTelecomManager;
+    @Mock TelephonyManager mTelephonyManager;
+    @Mock Phone mPhone;
+    @Mock SharedPreferences mSharedPreferences;
+    @Mock NotificationManager mNotificationManager;
+    @Mock SubscriptionInfo mSubscriptionInfo;
+    @Mock Resources mResources;
+    @Mock ServiceState mServiceState;
+    @Mock CarrierConfigManager mCarrierConfigManager;
+
+    private Phone[] mPhones;
+    private NotificationMgr mNotificationMgr;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mPhones = new Phone[]{mPhone};
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+        when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM);
+        when(mApp.getSharedPreferences(anyString(), anyInt())).thenReturn(mSharedPreferences);
+
+        when(mApp.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(mApp.getSystemService(Context.STATUS_BAR_SERVICE)).thenReturn(mStatusBarManager);
+        when(mApp.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        when(mApp.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)).thenReturn(
+                mSubscriptionManager);
+        when(mApp.getSystemServiceName(TelecomManager.class)).thenReturn(Context.TELECOM_SERVICE);
+        when(mApp.getSystemService(TelecomManager.class)).thenReturn(mTelecomManager);
+        when(mApp.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager);
+        when(mApp.getSystemServiceName(CarrierConfigManager.class)).thenReturn(
+                Context.CARRIER_CONFIG_SERVICE);
+        when(mApp.getSystemService(CarrierConfigManager.class)).thenReturn(mCarrierConfigManager);
+        when(mApp.getSystemServiceName(CarrierConfigManager.class)).thenReturn(
+                Context.CARRIER_CONFIG_SERVICE);
+        when(mApp.getSystemService(CarrierConfigManager.class)).thenReturn(mCarrierConfigManager);
+
+        when(mApp.createPackageContextAsUser(any(), eq(0), any())).thenReturn(mApp);
+        when(mApp.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(mNotificationManager);
+        when(mUserManager.getSerialNumbersOfUsers(true)).thenReturn(
+                new long[]{SERIAL_NUMBER_OF_USER});
+        when(mUserManager.getUserForSerialNumber(eq(SERIAL_NUMBER_OF_USER))).thenReturn(
+                UserHandle.SYSTEM);
+        when(mApp.getResources()).thenReturn(mResources);
+        when(mResources.getString(R.string.labelCF)).thenReturn(TEST_LABEL_CF);
+        ApplicationInfo appWithSdkS = buildApplicationInfo(Build.VERSION_CODES.S);
+        when(mApp.getApplicationInfo()).thenReturn(appWithSdkS);
+        when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
+        when(mTelephonyManager.getServiceState()).thenReturn(mServiceState);
+
+        mNotificationMgr = new NotificationMgr(mApp);
+    }
+
+    @Test
+    public void testUpdateCfi_visible_noActiveSubscription_notificationNeverSent()
+            throws Exception {
+        // Given no active subscription available
+        when(mSubscriptionManager.getActiveSubscriptionInfo(eq(TEST_SUB_ID))).thenReturn(null);
+
+        // When updateCfi method is called
+        mNotificationMgr.updateCfi(TEST_SUB_ID, /*visible=*/true, /*isFresh=*/false);
+
+        // Then the notification should never be sent
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateCfi_visible_hasActiveSub_singleSIM_notificationSent() throws Exception {
+        when(mTelephonyManager.getPhoneCount()).thenReturn(1);
+        when(mSubscriptionManager.getActiveSubscriptionInfo(eq(TEST_SUB_ID))).thenReturn(
+                mSubscriptionInfo);
+
+        mNotificationMgr.updateCfi(TEST_SUB_ID, /*visible=*/true, /*isFresh=*/false);
+
+        verifyNotificationSentWithChannelId(NotificationChannelController.CHANNEL_ID_CALL_FORWARD);
+    }
+
+    @Test
+    public void testUpdateCfi_visible_hasActiveSub_multiSIM_notificationSentWithoutDisplayName()
+            throws Exception {
+        when(mTelephonyManager.getPhoneCount()).thenReturn(2);
+        when(mSubscriptionManager.getActiveSubscriptionInfo(eq(TEST_SUB_ID))).thenReturn(
+                mSubscriptionInfo);
+        when(mSubscriptionInfo.getDisplayName()).thenReturn(null);
+
+        mNotificationMgr.updateCfi(TEST_SUB_ID, /*visible=*/true, /*isFresh=*/false);
+
+        verifyNotificationSentWithChannelId(NotificationChannelController.CHANNEL_ID_CALL_FORWARD);
+    }
+
+    @Test
+    public void testUpdateCfi_visible_hasActiveSub_multiSIM_notificationSentWithDisplayName()
+            throws Exception {
+        when(mTelephonyManager.getPhoneCount()).thenReturn(2);
+        when(mSubscriptionManager.getActiveSubscriptionInfo(eq(TEST_SUB_ID))).thenReturn(
+                mSubscriptionInfo);
+        when(mSubscriptionInfo.getDisplayName()).thenReturn(TEST_SUB_INFO_DISPLAY_NAME);
+
+        mNotificationMgr.updateCfi(TEST_SUB_ID, /*visible=*/true, /*isFresh=*/false);
+
+        verifyNotificationSentWithChannelId(NotificationChannelController.CHANNEL_ID_CALL_FORWARD);
+    }
+
+    @Test
+    public void testUpdateCfi_invisible_hasUnmanagedProfile_notificationCanceled()
+            throws Exception {
+        when(mUserManager.isManagedProfile(anyInt())).thenReturn(false);
+
+        mNotificationMgr.updateCfi(TEST_SUB_ID, /*visible=*/false, /*isFresh=*/false);
+
+        verify(mNotificationManager).cancel(any(), anyInt());
+    }
+
+    @Test
+    public void testUpdateCfi_invisible_allProfilesAreManaged_notificationNeverCanceled()
+            throws Exception {
+        when(mUserManager.isManagedProfile(anyInt())).thenReturn(true);
+
+        mNotificationMgr.updateCfi(TEST_SUB_ID, /*visible=*/false, /*isFresh=*/false);
+
+        verify(mNotificationManager, never()).cancel(any(), anyInt());
+    }
+
+    @Test
+    public void testShowDataRoamingNotification_roamingOn() throws Exception {
+        mNotificationMgr.showDataRoamingNotification(TEST_SUB_ID, /*roamingOn=*/true);
+
+        verifyNotificationSentWithChannelId(
+                NotificationChannelController.CHANNEL_ID_MOBILE_DATA_STATUS);
+    }
+
+    @Test
+    public void testShowDataRoamingNotification_roamingOff() throws Exception {
+        mNotificationMgr.showDataRoamingNotification(TEST_SUB_ID, /*roamingOn=*/false);
+
+        verifyNotificationSentWithChannelId(
+                NotificationChannelController.CHANNEL_ID_MOBILE_DATA_STATUS);
+    }
+
+    @Test
+    public void testHideDataRoamingNotification() {
+        mNotificationMgr.hideDataRoamingNotification();
+
+        verify(mNotificationManager).cancel(any(), eq(DATA_ROAMING_NOTIFICATION));
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_justOutOfService_notificationNeverSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(2000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_oosEnoughTime_selectionVisibleToUser_notificationSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+        when(mTelephonyManager.isManualNetworkSelectionAllowed()).thenReturn(true);
+        PersistableBundle config = new PersistableBundle();
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        // TODO: use effective TestLooper time eclipse instead of sleeping
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verifyNotificationSentWithChannelId(NotificationChannelController.CHANNEL_ID_ALERT);
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_invalidSubscription_notificationNotSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+        when(mTelephonyManager.isManualNetworkSelectionAllowed()).thenReturn(true);
+        PersistableBundle config = new PersistableBundle();
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE,
+                INVALID_SUBSCRIPTION_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE,
+                INVALID_SUBSCRIPTION_ID);
+
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_nullCarrierConfig_notificationNotSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(null);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_userNotAllowedToChooseOperator_notificationNotSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        PersistableBundle config = new PersistableBundle();
+        // User is NOT allowed to choose operator
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        when(mTelephonyManager.isManualNetworkSelectionAllowed()).thenReturn(false);
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_OverrideHideCarrierNetworkSelection_notificationNotSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        PersistableBundle config = new PersistableBundle();
+        // Hide network selection menu
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        when(mTelephonyManager.isManualNetworkSelectionAllowed()).thenReturn(false);
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_simPreventManualSelection_notificationNotSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        PersistableBundle config = new PersistableBundle();
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        // SIM card can prevent manual network selection which is forbidden
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, true);
+        when(mTelephonyManager.isManualNetworkSelectionAllowed()).thenReturn(false);
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_worldMode_userSetLTE_notificationNotSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        PersistableBundle config = new PersistableBundle();
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+
+        // World mode is on
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL, true);
+        // User set Network mode as LTE
+        when(mTelephonyManager.getAllowedNetworkTypesForReason(
+                        TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER)).thenReturn(
+                (long) (RAF_LTE | RAF_LTE_CA | RAF_IS95A | RAF_IS95B | RAF_1xRTT | RAF_EVDO_0
+                        | RAF_EVDO_A | RAF_EVDO_B | RAF_EHRPD));
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_worldMode_userSetTDSCDMA_notSupported_notifNotSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        PersistableBundle config = new PersistableBundle();
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+
+        // World mode is on
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL, true);
+        // User set Network mode as NETWORK_MODE_LTE_TDSCDMA_GSM
+        when(mTelephonyManager.getAllowedNetworkTypesForReason(
+                TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER)).thenReturn(
+                (long) (RAF_LTE | RAF_LTE_CA | RAF_TD_SCDMA | RAF_GSM | RAF_GPRS | RAF_EDGE));
+        // But TDSCDMA is NOT supported
+        config.putBoolean(CarrierConfigManager.KEY_SUPPORT_TDSCDMA_BOOL, false);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_worldMode_userSetWCDMA_notificationSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        PersistableBundle config = new PersistableBundle();
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+
+        // World mode is on
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL, true);
+        // User set Network mode as NETWORK_MODE_LTE_TDSCDMA_GSM
+        when(mTelephonyManager.getAllowedNetworkTypesForReason(
+                        TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER)).thenReturn(
+                (long) (RAF_LTE | RAF_LTE_CA | RAF_GSM | RAF_GPRS | RAF_EDGE | RAF_HSUPA | RAF_HSDPA
+                        | RAF_HSPA | RAF_HSPAP | RAF_UMTS));
+        // But TDSCDMA is NOT supported
+        config.putBoolean(CarrierConfigManager.KEY_SUPPORT_TDSCDMA_BOOL, false);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verifyNotificationSentWithChannelId(NotificationChannelController.CHANNEL_ID_ALERT);
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_worldPhone_networkSelectionNotHide_notificationSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        PersistableBundle config = new PersistableBundle();
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        // World mode is off
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL, false);
+        // World phone is on
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verifyNotificationSentWithChannelId(NotificationChannelController.CHANNEL_ID_ALERT);
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_gsmBasicOptionOn_notificationSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        PersistableBundle config = new PersistableBundle();
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        // World phone is on
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL, true);
+        // World mode is off
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL, false);
+        when(mTelephonyManager.getPhoneType()).thenReturn(TelephonyManager.PHONE_TYPE_GSM);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verifyNotificationSentWithChannelId(NotificationChannelController.CHANNEL_ID_ALERT);
+    }
+
+    @Test
+    public void testUpdateNetworkSelection_gsmBasicOptionOff_notificationNotSent()
+            throws Exception {
+        prepareResourcesForNetworkSelection();
+
+        PersistableBundle config = new PersistableBundle();
+        config.putBoolean(CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
+        config.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
+        config.putBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL, false);
+        // World mode is off
+        config.putBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL, false);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUB_ID)).thenReturn(config);
+        when(mTelephonyManager.getPhoneType()).thenReturn(TelephonyManager.PHONE_TYPE_CDMA);
+
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException ignored) {
+        }
+        mNotificationMgr.updateNetworkSelection(ServiceState.STATE_OUT_OF_SERVICE, TEST_SUB_ID);
+
+        verify(mNotificationManager, never()).notify(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testShowLimitedSimFunctionWarningNotification_forTheFirstTime_notificationSent()
+            throws Exception {
+        when(mResources.getText(R.string.limited_sim_function_notification_message)).thenReturn(
+                CARRIER_NAME);
+        when(mResources.getText(
+                R.string.limited_sim_function_with_phone_num_notification_message)).thenReturn(
+                "123");
+
+        mNotificationMgr.showLimitedSimFunctionWarningNotification(TEST_SUB_ID, CARRIER_NAME);
+
+        verifyNotificationSentWithChannelId(
+                NotificationChannelController.CHANNEL_ID_SIM_HIGH_PRIORITY);
+    }
+
+    @Test
+    public void testShowLimitedSimFunctionWarningNotification_consecutiveCall_notificationSentOnce()
+            throws Exception {
+        when(mResources.getText(R.string.limited_sim_function_notification_message)).thenReturn(
+                CARRIER_NAME);
+        when(mResources.getText(
+                R.string.limited_sim_function_with_phone_num_notification_message)).thenReturn(
+                "123");
+
+        // Call the method TWICE with the same subscription
+        mNotificationMgr.showLimitedSimFunctionWarningNotification(TEST_SUB_ID, CARRIER_NAME);
+        mNotificationMgr.showLimitedSimFunctionWarningNotification(TEST_SUB_ID, CARRIER_NAME);
+
+        // Verify the notification is only sent ONCE
+        verifyNotificationSentWithChannelId(
+                NotificationChannelController.CHANNEL_ID_SIM_HIGH_PRIORITY);
+    }
+
+    @Test
+    public void testDismissLimitedSimFunctionWarningNotification_noShowCalledBefore_noCancelSent()
+            throws Exception {
+        // showLimitedSimFunctionWarningNotification was never called before
+
+        mNotificationMgr.dismissLimitedSimFunctionWarningNotification(TEST_SUB_ID);
+
+        verify(mNotificationManager, never()).cancel(any(), anyInt());
+    }
+
+    @Test
+    public void testDismissLimitedSimFunctionWarningNotification_showCalledBefore_cancelSent()
+            throws Exception {
+        when(mResources.getText(R.string.limited_sim_function_notification_message)).thenReturn(
+                CARRIER_NAME);
+        when(mResources.getText(
+                R.string.limited_sim_function_with_phone_num_notification_message)).thenReturn(
+                "123");
+        mNotificationMgr.showLimitedSimFunctionWarningNotification(TEST_SUB_ID, CARRIER_NAME);
+
+        mNotificationMgr.dismissLimitedSimFunctionWarningNotification(TEST_SUB_ID);
+
+        verify(mNotificationManager).cancel(any(), eq(LIMITED_SIM_FUNCTION_NOTIFICATION));
+    }
+
+    private ApplicationInfo buildApplicationInfo(int targetSdkVersion) {
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.targetSdkVersion = targetSdkVersion;
+        return applicationInfo;
+    }
+
+    private void verifyNotificationSentWithChannelId(String expectedNotificationChannelId) {
+        ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(
+                Notification.class);
+        verify(mNotificationManager).notify(any(), anyInt(), notificationArgumentCaptor.capture());
+        Notification capturedNotification = notificationArgumentCaptor.getAllValues().get(0);
+        assertThat(capturedNotification.getChannelId()).isEqualTo(expectedNotificationChannelId);
+    }
+
+    private void prepareResourcesForNetworkSelection() {
+        when(mSharedPreferences.getString(Phone.NETWORK_SELECTION_NAME_KEY + TEST_SUB_ID,
+                "")).thenReturn(TEST_SELECTED_NETWORK_OPERATOR_NAME);
+        when(mResources.getBoolean(
+                com.android.internal.R.bool.skip_restoring_network_selection)).thenReturn(false);
+        when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mApp.getString(R.string.mobile_network_settings_package)).thenReturn(
+                MOBILE_NETWORK_SELECTION_PACKAGE);
+        when(mApp.getString(R.string.mobile_network_settings_class)).thenReturn(
+                MOBILE_NETWORK_SELECTION_CLASS);
+    }
+
+    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);
+    }
+}
diff --git a/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java b/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java
index 7f9efdc..28a7b02 100644
--- a/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java
+++ b/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java
@@ -17,19 +17,104 @@
 package com.android.services.telephony;
 
 import static android.media.ToneGenerator.TONE_PROP_PROMPT;
+import static android.media.ToneGenerator.TONE_SUP_BUSY;
 
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.TestCase.assertEquals;
 
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
 import android.telephony.DisconnectCause;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.TelephonyTestBase;
+import com.android.internal.telephony.GsmCdmaPhone;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.phone.common.R;
+
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Locale;
+
 
 @RunWith(AndroidJUnit4.class)
-public class DisconnectCauseUtilTest {
+public class DisconnectCauseUtilTest extends TelephonyTestBase {
+
+    // constants
+    public static final int PHONE_ID = 123;
+    public static final String EMPTY_STRING = "";
+
+    // dynamic
+    private Context mContext;
+    private HashMap<InstanceKey, Object> mOldInstances = new HashMap<InstanceKey, Object>();
+    private ArrayList<InstanceKey> mInstanceKeys = new ArrayList<InstanceKey>();
+
+    //Mocks
+    @Mock
+    private GsmCdmaPhone mMockPhone;
+
+    // inner classes
+    private static class InstanceKey {
+        public final Class mClass;
+        public final String mInstName;
+        public final Object mObj;
+
+        InstanceKey(final Class c, final String instName, final Object obj) {
+            mClass = c;
+            mInstName = instName;
+            mObj = obj;
+        }
+
+        @Override
+        public int hashCode() {
+            return (mClass.getName().hashCode() * 31 + mInstName.hashCode()) * 31;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null || !(obj instanceof InstanceKey)) {
+                return false;
+            }
+
+            InstanceKey other = (InstanceKey) obj;
+            return (other.mClass == mClass && other.mInstName.equals(mInstName)
+                    && other.mObj == mObj);
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        // objects that call static getInstance()
+        mMockPhone = Mockito.mock(GsmCdmaPhone.class);
+        mContext = InstrumentationRegistry.getTargetContext();
+        // set mocks
+        setSinglePhone();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // restoreInstance.
+        // Not doing so will potentially "confuse" other tests with the mocked instance
+        restoreInstance(PhoneFactory.class, "sPhones", null);
+        super.tearDown();
+    }
+
+
     /**
      * Verifies that a call drop due to loss of WIFI results in a disconnect cause of error and that
      * the label, description and tone are all present.
@@ -43,4 +128,109 @@
         assertNotNull(tcCause.getDescription());
         assertNotNull(tcCause.getReason());
     }
+
+    /**
+     *  ensure the default behavior was not changed when a disconnect cause comes in as
+     *  DisconnectCause.ERROR_UNSPECIFIED
+     */
+    @Test
+    public void testDefaultDisconnectCauseBehaviorForCauseNotInCarrierBusyToneArray() {
+        android.telecom.DisconnectCause tcCause = DisconnectCauseUtil.toTelecomDisconnectCause(
+                DisconnectCause.ERROR_UNSPECIFIED, EMPTY_STRING, PHONE_ID);
+        // CODE
+        assertEquals(android.telecom.DisconnectCause.ERROR, tcCause.getCode());
+        // LABEL
+        safeAssertLabel(null, tcCause);
+        // TONE
+        assertEquals(TONE_PROP_PROMPT, tcCause.getTone());
+    }
+
+    /**
+     *  Simulate a Carrier classifying the DisconnectCause.ERROR_UNSPECIFIED as a
+     *  DisconnectCause.BUSY.  The code, label, and tone should match DisconnectCause.BUSY.
+     */
+    @Test
+    public void testCarrierSetDisconnectCauseInBusyToneArray() {
+        int[] carrierBusyArr = {DisconnectCause.BUSY, DisconnectCause.ERROR_UNSPECIFIED};
+        PersistableBundle config = new PersistableBundle();
+
+        config.putIntArray(
+                CarrierConfigManager.KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
+                carrierBusyArr);
+
+        android.telecom.DisconnectCause tcCause =
+                DisconnectCauseUtil.toTelecomDisconnectCause(
+                        DisconnectCause.ERROR_UNSPECIFIED, -1,
+                        EMPTY_STRING, PHONE_ID, null, config);
+
+        // CODE
+        assertEquals(android.telecom.DisconnectCause.BUSY, tcCause.getCode());
+        // LABEL
+        safeAssertLabel(R.string.callFailed_userBusy, tcCause);
+        // TONE
+        assertEquals(TONE_SUP_BUSY, tcCause.getTone());
+    }
+
+    private void setSinglePhone() throws Exception {
+        Phone[] mPhones = new Phone[]{mMockPhone};
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+    }
+
+
+    protected synchronized void replaceInstance(final Class c, final String instanceName,
+            final Object obj, final Object newValue)
+            throws Exception {
+        Field field = c.getDeclaredField(instanceName);
+        field.setAccessible(true);
+
+        InstanceKey key = new InstanceKey(c, instanceName, obj);
+        if (!mOldInstances.containsKey(key)) {
+            mOldInstances.put(key, field.get(obj));
+            mInstanceKeys.add(key);
+        }
+        field.set(obj, newValue);
+    }
+
+    protected synchronized void restoreInstance(final Class c, final String instanceName,
+            final Object obj) throws Exception {
+        InstanceKey key = new InstanceKey(c, instanceName, obj);
+        if (mOldInstances.containsKey(key)) {
+            Field field = c.getDeclaredField(instanceName);
+            field.setAccessible(true);
+            field.set(obj, mOldInstances.get(key));
+            mOldInstances.remove(key);
+            mInstanceKeys.remove(key);
+        }
+    }
+
+    private Resources getResourcesForLocale(Context context, Locale locale) {
+        Configuration config = new Configuration();
+        config.setToDefaults();
+        config.setLocale(locale);
+        Context localeContext = context.createConfigurationContext(config);
+        return localeContext.getResources();
+    }
+
+    private void safeAssertLabel(Integer resourceId,
+            android.telecom.DisconnectCause disconnectCause) {
+        Resources r = getResourcesForLocale(mContext, Locale.US);
+        if (resourceId == null || r == null) {
+            return;
+        }
+        String label = r.getString(resourceId);
+        assertEquals(label, disconnectCause.getLabel());
+    }
+
+    /**
+     * Verifies that an ICC_ERROR disconnect cause generates a message which mentions there is no
+     * SIM.
+     */
+    @Test
+    public void testIccError() {
+        android.telecom.DisconnectCause tcCause = DisconnectCauseUtil.toTelecomDisconnectCause(
+                DisconnectCause.ICC_ERROR);
+        assertEquals(android.telecom.DisconnectCause.ERROR, tcCause.getCode());
+        assertNotNull(tcCause.getLabel());
+        assertNotNull(tcCause.getDescription());
+    }
 }
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
index efa906e..c4d22ab 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
@@ -49,6 +49,7 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.RadioAccessFamily;
 import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -396,9 +397,6 @@
         // Slot 1 has more capabilities
         setPhoneRadioAccessFamily(slot0Phone, RadioAccessFamily.RAF_GSM);
         setPhoneRadioAccessFamily(slot1Phone, RadioAccessFamily.RAF_LTE);
-        // Slot 1 has SIM inserted.
-        setSlotHasIccCard(SLOT_0_PHONE_ID, false /*isInserted*/);
-        setSlotHasIccCard(SLOT_1_PHONE_ID, true /*isInserted*/);
 
         Phone resultPhone = mTestConnectionService.getFirstPhoneForEmergencyCall();
 
@@ -511,9 +509,6 @@
         // Make Capability the same
         setPhoneRadioAccessFamily(slot0Phone, RadioAccessFamily.RAF_LTE);
         setPhoneRadioAccessFamily(slot1Phone, RadioAccessFamily.RAF_LTE);
-        // Two SIMs inserted
-        setSlotHasIccCard(SLOT_0_PHONE_ID, true /*isInserted*/);
-        setSlotHasIccCard(SLOT_1_PHONE_ID, true /*isInserted*/);
 
         Phone resultPhone = mTestConnectionService.getFirstPhoneForEmergencyCall();
 
@@ -542,9 +537,6 @@
         // Make Capability the same
         setPhoneRadioAccessFamily(slot0Phone, RadioAccessFamily.RAF_LTE);
         setPhoneRadioAccessFamily(slot1Phone, RadioAccessFamily.RAF_LTE);
-        // Slot 0 has SIM inserted.
-        setSlotHasIccCard(SLOT_0_PHONE_ID, true /*isInserted*/);
-        setSlotHasIccCard(SLOT_1_PHONE_ID, false /*isInserted*/);
 
         Phone resultPhone = mTestConnectionService.getFirstPhoneForEmergencyCall();
 
@@ -573,9 +565,35 @@
         // Make Capability the same
         setPhoneRadioAccessFamily(slot0Phone, RadioAccessFamily.RAF_LTE);
         setPhoneRadioAccessFamily(slot1Phone, RadioAccessFamily.RAF_LTE);
-        // Slot 1 has SIM inserted.
-        setSlotHasIccCard(SLOT_0_PHONE_ID, false /*isInserted*/);
-        setSlotHasIccCard(SLOT_1_PHONE_ID, true /*isInserted*/);
+
+        Phone resultPhone = mTestConnectionService.getFirstPhoneForEmergencyCall();
+
+        assertEquals(slot1Phone, resultPhone);
+    }
+
+    /**
+     * Prerequisites:
+     * - MSIM Device with one ESIM, only slot 1 inserted has PSIM inserted
+     * - Both phones have the same capability
+     *
+     * Result: getFirstPhoneForEmergencyCall returns the slot 1 phone because it is the only one
+     * with a SIM inserted
+     */
+    @Test
+    @SmallTest
+    public void testEqualCapabilitySim1Inserted_WithOneEsim() {
+        Phone slot0Phone = makeTestPhone(SLOT_0_PHONE_ID, ServiceState.STATE_OUT_OF_SERVICE,
+            false /*isEmergencyOnly*/);
+        Phone slot1Phone = makeTestPhone(SLOT_1_PHONE_ID, ServiceState.STATE_OUT_OF_SERVICE,
+            false /*isEmergencyOnly*/);
+        setDefaultPhone(slot0Phone);
+        setupDeviceConfig(slot0Phone, slot1Phone, SLOT_0_PHONE_ID);
+        when(slot0Phone.getSubId()).thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        setPhoneSlotState(SLOT_0_PHONE_ID, TelephonyManager.SIM_STATE_READY);
+        setPhoneSlotState(SLOT_1_PHONE_ID, TelephonyManager.SIM_STATE_READY);
+        // Make Capability the same
+        setPhoneRadioAccessFamily(slot0Phone, RadioAccessFamily.RAF_LTE);
+        setPhoneRadioAccessFamily(slot1Phone, RadioAccessFamily.RAF_LTE);
 
         Phone resultPhone = mTestConnectionService.getFirstPhoneForEmergencyCall();
 
@@ -604,9 +622,6 @@
         // Make Capability the same
         setPhoneRadioAccessFamily(slot0Phone, RadioAccessFamily.RAF_GSM);
         setPhoneRadioAccessFamily(slot1Phone, RadioAccessFamily.RAF_LTE);
-        // No SIMs inserted
-        setSlotHasIccCard(SLOT_0_PHONE_ID, false /*isInserted*/);
-        setSlotHasIccCard(SLOT_1_PHONE_ID, false /*isInserted*/);
 
         Phone resultPhone = mTestConnectionService.getFirstPhoneForEmergencyCall();
 
@@ -634,9 +649,63 @@
         // Make Capability the same
         setPhoneRadioAccessFamily(slot0Phone, RadioAccessFamily.RAF_UNKNOWN);
         setPhoneRadioAccessFamily(slot1Phone, RadioAccessFamily.RAF_UNKNOWN);
-        // No SIMs inserted
-        setSlotHasIccCard(SLOT_0_PHONE_ID, false /*isInserted*/);
-        setSlotHasIccCard(SLOT_1_PHONE_ID, false /*isInserted*/);
+
+        Phone resultPhone = mTestConnectionService.getFirstPhoneForEmergencyCall();
+
+        assertEquals(slot0Phone, resultPhone);
+    }
+
+    /**
+     * Prerequisites:
+     * - MSIM Device, no SIMs inserted (one ESIM)
+     * - Both SIMs have the same capability (Unknown)
+     *
+     * Result: getFirstPhoneForEmergencyCall returns the slot 0 phone, since it is the first slot.
+     */
+    @Test
+    @SmallTest
+    public void testEqualCapabilityNoSimsInserted_WithOneESim() {
+        Phone slot0Phone = makeTestPhone(SLOT_0_PHONE_ID, ServiceState.STATE_OUT_OF_SERVICE,
+            false /*isEmergencyOnly*/);
+        Phone slot1Phone = makeTestPhone(SLOT_1_PHONE_ID, ServiceState.STATE_OUT_OF_SERVICE,
+            false /*isEmergencyOnly*/);
+        setDefaultPhone(slot0Phone);
+        setupDeviceConfig(slot0Phone, slot1Phone, SLOT_0_PHONE_ID);
+        setPhoneSlotState(SLOT_0_PHONE_ID, TelephonyManager.SIM_STATE_ABSENT);
+        when(slot1Phone.getSubId()).thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        setPhoneSlotState(SLOT_1_PHONE_ID, TelephonyManager.SIM_STATE_READY);
+        // Make Capability the samesvim
+        setPhoneRadioAccessFamily(slot0Phone, RadioAccessFamily.RAF_UNKNOWN);
+        setPhoneRadioAccessFamily(slot1Phone, RadioAccessFamily.RAF_UNKNOWN);
+
+        Phone resultPhone = mTestConnectionService.getFirstPhoneForEmergencyCall();
+
+        assertEquals(slot0Phone, resultPhone);
+    }
+
+    /**
+     * Prerequisites:
+     * - MSIM Device, both ESIMS (no profile activated)
+     * - Both phones have the same capability (Unknown)
+     *
+     * Result: getFirstPhoneForEmergencyCall returns the slot 0 phone, since it is the first slot.
+     */
+    @Test
+    @SmallTest
+    public void testEqualCapabilityNoSimsInserted_WithTwoESims() {
+        Phone slot0Phone = makeTestPhone(SLOT_0_PHONE_ID, ServiceState.STATE_OUT_OF_SERVICE,
+            false /*isEmergencyOnly*/);
+        Phone slot1Phone = makeTestPhone(SLOT_1_PHONE_ID, ServiceState.STATE_OUT_OF_SERVICE,
+            false /*isEmergencyOnly*/);
+        setDefaultPhone(slot0Phone);
+        setupDeviceConfig(slot0Phone, slot1Phone, SLOT_0_PHONE_ID);
+        when(slot0Phone.getSubId()).thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        setPhoneSlotState(SLOT_0_PHONE_ID, TelephonyManager.SIM_STATE_READY);
+        when(slot1Phone.getSubId()).thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        setPhoneSlotState(SLOT_1_PHONE_ID, TelephonyManager.SIM_STATE_READY);
+        // Make Capability the sames
+        setPhoneRadioAccessFamily(slot0Phone, RadioAccessFamily.RAF_UNKNOWN);
+        setPhoneRadioAccessFamily(slot1Phone, RadioAccessFamily.RAF_UNKNOWN);
 
         Phone resultPhone = mTestConnectionService.getFirstPhoneForEmergencyCall();
 
@@ -1497,10 +1566,6 @@
         when(mSubscriptionManagerProxy.getSimStateForSlotIdx(slotId)).thenReturn(slotState);
     }
 
-    private void setSlotHasIccCard(int slotId, boolean isInserted) {
-        when(mTelephonyManagerProxy.hasIccCard(slotId)).thenReturn(isInserted);
-    }
-
     private void setDefaultPhone(Phone phone) {
         when(mPhoneFactoryProxy.getDefaultPhone()).thenReturn(phone);
     }
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
index 388fd29..c996e5f 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
@@ -14,6 +14,7 @@
 import static org.mockito.Mockito.when;
 
 import android.os.Bundle;
+import android.os.PersistableBundle;
 import android.telecom.Connection;
 import android.telephony.CarrierConfigManager;
 import android.telephony.DisconnectCause;
@@ -25,6 +26,7 @@
 import com.android.internal.telephony.d2d.DtmfTransport;
 import com.android.internal.telephony.d2d.RtpTransport;
 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
+import com.android.phone.PhoneGlobals;
 import com.android.phone.R;
 
 import org.junit.Before;
@@ -212,4 +214,47 @@
             fail("refreshConferenceSupported threw ClassCastException");
         }
     }
+
+    /**
+     * Tests TelephonyConnection#getCarrierConfig never returns a null given all cases that can
+     * cause a potential null.
+     */
+    @Test
+    public void testGetCarrierConfigBehaviorWithNull() throws Exception {
+        TestTelephonyConnectionSimple c = new TestTelephonyConnectionSimple();
+
+        // case: return a valid carrier config (good case)
+        when(c.mPhoneGlobals.getCarrierConfigForSubId(c.getPhone().getSubId())).
+                thenReturn(CarrierConfigManager.getDefaultConfig());
+        assertNotNull(c.getCarrierConfig());
+
+        // case: PhoneGlobals.getInstance().getCarrierConfigForSubId(int) returns null
+        when(c.mPhoneGlobals.getCarrierConfigForSubId(c.getPhone().getSubId()))
+                .thenReturn(null);
+        assertNotNull(c.getCarrierConfig());
+
+        // case: phone is null
+        c.setMockPhone(null);
+        assertNull(c.getPhone());
+        assertNotNull(c.getCarrierConfig());
+    }
+
+    /**
+     * Tests the behavior of TelephonyConnection#isRttMergeSupported(@NonNull PersistableBundle).
+     * Note, the function should be able to handle an empty PersistableBundle and should NEVER
+     * receive a null object as denoted in by @NonNull annotation.
+     */
+    @Test
+    public void testIsRttMergeSupportedBehavior() {
+        TestTelephonyConnection c = new TestTelephonyConnection();
+        //  ensure isRttMergeSupported(PersistableBundle) does not throw NPE when given an Empty PB
+        assertFalse(c.isRttMergeSupported(new PersistableBundle()));
+
+        // simulate the passing situation
+        c.getCarrierConfigBundle().putBoolean(
+                CarrierConfigManager.KEY_ALLOW_MERGING_RTT_CALLS_BOOL,
+                true);
+        assertTrue(c.isRttMergeSupported(c.getCarrierConfig()));
+    }
+
 }
diff --git a/tests/src/com/android/services/telephony/TelephonyManagerTest.java b/tests/src/com/android/services/telephony/TelephonyManagerTest.java
index cf1ae8f..89b558c 100644
--- a/tests/src/com/android/services/telephony/TelephonyManagerTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyManagerTest.java
@@ -15,11 +15,14 @@
  */
 
 package com.android.services.telephony;
-
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -28,6 +31,8 @@
 import android.app.PropertyInvalidatedCache;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -37,6 +42,8 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.IPhoneSubInfo;
+import com.android.internal.telephony.PhoneConstants;
 
 import org.junit.After;
 import org.junit.Before;
@@ -61,8 +68,10 @@
     private static final int TEST_SUBID_2 = 2;
 
     private ITelephony mMockITelephony;
+    private IPhoneSubInfo mMockIPhoneSubInfo;
     private SubscriptionManager mMockSubscriptionManager;
     private Context mMockContext;
+    private final PackageManager mPackageManager = mock(PackageManager.class);
 
     private TelephonyManager mTelephonyManager;
 
@@ -89,18 +98,23 @@
                     }
                     return null;
                 }
+                @Override
+                public PackageManager getPackageManager() {
+                    return mPackageManager;
+                }
             };
 
     @Before
     public void setUp() throws Exception {
         mMockITelephony = mock(ITelephony.class);
+        mMockIPhoneSubInfo = mock(IPhoneSubInfo.class);
         mMockSubscriptionManager = mock(SubscriptionManager.class);
         mMockContext = mock(Context.class);
         when(mMockContext.getSystemService(eq(Context.TELEPHONY_SUBSCRIPTION_SERVICE)))
                 .thenReturn(mMockSubscriptionManager);
-
         mTelephonyManager = new TelephonyManager(mContext);
         TelephonyManager.setupITelephonyForTest(mMockITelephony);
+        TelephonyManager.setupIPhoneSubInfoForTest(mMockIPhoneSubInfo);
         TelephonyManager.enableServiceHandleCaching();
     }
 
@@ -218,4 +232,32 @@
         verify(mMockITelephony, times(1)).getSubIdForPhoneAccountHandle(eq(TEST_HANDLE2),
                 anyString(), anyString());
     }
+
+    @Test
+    public void testGetSimServiceTable_USIM() throws RemoteException {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, true));
+        when(mMockIPhoneSubInfo.getSimServiceTable(anyInt(), anyInt())).thenReturn("12345");
+        assertEquals("12345", mTelephonyManager.getSimServiceTable(PhoneConstants.APPTYPE_USIM));
+        verify(mMockIPhoneSubInfo, times(1)).getSimServiceTable(anyInt(), anyInt());
+    }
+
+    @Test
+    public void testGetSimServiceTable_ISIM() throws RemoteException {
+        when(mMockIPhoneSubInfo.getIsimIst(anyInt())).thenReturn("12345");
+        assertEquals("12345", mTelephonyManager.getSimServiceTable(PhoneConstants.APPTYPE_ISIM));
+        verify(mMockIPhoneSubInfo, times(1)).getIsimIst(anyInt());
+    }
+
+    @Test
+    public void testGetSimServiceTable_RUSIM() throws RemoteException {
+        assumeFalse(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, false));
+        assertEquals(null, mTelephonyManager.getSimServiceTable(PhoneConstants.APPTYPE_RUIM));
+    }
+
+    private boolean hasFeature(String feature, boolean status) {
+        doReturn(status)
+                .when(mPackageManager).hasSystemFeature(
+                        PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION);
+        return mContext.getPackageManager().hasSystemFeature(feature);
+    }
 }
diff --git a/tests/src/com/android/services/telephony/TestTelephonyConnectionSimple.java b/tests/src/com/android/services/telephony/TestTelephonyConnectionSimple.java
new file mode 100644
index 0000000..9dc2551
--- /dev/null
+++ b/tests/src/com/android/services/telephony/TestTelephonyConnectionSimple.java
@@ -0,0 +1,72 @@
+/*
+ * 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;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.AttributionSource;
+import android.content.Context;
+import android.os.Process;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.phone.PhoneGlobals;
+
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class TestTelephonyConnectionSimple extends TelephonyConnection{
+
+    @Mock
+    Context mMockContext;
+
+    @Mock
+    PhoneGlobals mPhoneGlobals;
+
+    private Phone mMockPhone;
+
+    public TelephonyConnection cloneConnection() {
+        return this;
+    }
+
+    public TestTelephonyConnectionSimple(){
+        super(null, null, android.telecom.Call.Details.DIRECTION_INCOMING);
+        MockitoAnnotations.initMocks(this);
+
+        AttributionSource attributionSource = new AttributionSource.Builder(
+                Process.myUid()).build();
+
+        mMockPhone    = mock(Phone.class);
+        mMockContext  = mock(Context.class);
+        mPhoneGlobals = mock(PhoneGlobals.class);
+
+        when(mMockPhone.getSubId()).thenReturn(1);
+    }
+
+    public void setMockPhone(Phone newPhone) {
+        mMockPhone = newPhone;
+    }
+
+    @Override
+    public Phone getPhone() {
+        return mMockPhone;
+    }
+
+}