Merge "Add DomainSelection owners for Telephony APIs" into main am: 9eba5b8ae6

Original change: https://android-review.googlesource.com/c/platform/frameworks/opt/telephony/+/2825690

Change-Id: Id6b7497ca55fefc01a539858175f1018f37360fd
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/proto/src/persist_atoms.proto b/proto/src/persist_atoms.proto
index 61e44a3..2e569f0 100644
--- a/proto/src/persist_atoms.proto
+++ b/proto/src/persist_atoms.proto
@@ -275,6 +275,8 @@
     optional int32 call_duration = 32;
     optional int32 last_known_rat = 33;
     optional int32 fold_state = 34;
+    optional int64 rat_switch_count_after_connected = 35;
+    optional bool handover_in_progress = 36;
 
     // Internal use only
     optional int64 setup_begin_millis = 10001;
@@ -378,6 +380,8 @@
     optional bool is_emergency_only = 10;
     optional bool is_internet_pdn_up = 11;
     optional int32 fold_state = 12;
+    optional bool override_voice_service = 13;
+    optional bool isDataEnabled = 14;
 
     // Internal use only
     optional int64 last_used_millis = 10001;
diff --git a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
index 6b44998..93f5ab0 100644
--- a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
@@ -116,14 +116,15 @@
                                     mPhone.getContext(),
                                     mPhone.getSubId(),
                                     CarrierConfigManager.KEY_EMERGENCY_NOTIFICATION_DELAY_INT,
-                                    CarrierConfigManager
-                                            .KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT);
+                                    CarrierConfigManager.KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT,
+                                    CarrierConfigManager.KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL);
                     if (b.isEmpty()) return;
 
                     for (Map.Entry<Integer, NotificationType> entry :
                             mNotificationTypeMap.entrySet()) {
                         NotificationType notificationType = entry.getValue();
                         notificationType.setDelay(b);
+                        notificationType.setEnabled(b);
                     }
                     handleConfigChanges();
                 });
@@ -430,6 +431,18 @@
         void setDelay(PersistableBundle bundle);
 
         /**
+         * Checks whether this Notification is enabled.
+         * @return {@code true} if this Notification is enabled, false otherwise
+         */
+        boolean isEnabled();
+
+        /**
+         * Sets whether this Notification is enabled. If disabled, it will not build notification.
+         * @param bundle PersistableBundle
+         */
+        void setEnabled(PersistableBundle bundle);
+
+        /**
          * returns notification type id.
          **/
         int getTypeId();
@@ -458,6 +471,7 @@
 
         private final int mTypeId;
         private int mDelay = UNINITIALIZED_DELAY_VALUE;
+        private boolean mEnabled = false;
 
         PrefNetworkNotification(int typeId) {
             this.mTypeId = typeId;
@@ -480,6 +494,28 @@
             return mDelay;
         }
 
+        /**
+         * Checks whether this Notification is enabled.
+         * @return {@code true} if this Notification is enabled, false otherwise
+         */
+        public boolean isEnabled() {
+            return mEnabled;
+        }
+
+        /**
+         * Sets whether this Notification is enabled. If disabled, it will not build notification.
+         * @param bundle PersistableBundle
+         */
+        public void setEnabled(PersistableBundle bundle) {
+            if (bundle == null) {
+                Rlog.e(LOG_TAG, "bundle is null");
+                return;
+            }
+            mEnabled = !bundle.getBoolean(
+                    CarrierConfigManager.KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL);
+            Rlog.i(LOG_TAG, "reading enabled notification pref network: " + mEnabled);
+        }
+
         public int getTypeId() {
             return mTypeId;
         }
@@ -497,10 +533,10 @@
          */
         public boolean sendMessage() {
             Rlog.i(LOG_TAG, "PrefNetworkNotification: sendMessage() w/values: "
-                    + "," + isPhoneStillRegistered() + "," + mDelay + "," + isGlobalMode()
-                    + "," + mSST.isRadioOn());
-            if (mDelay == UNINITIALIZED_DELAY_VALUE || isPhoneStillRegistered() || isGlobalMode()
-                    || isRadioOffOrAirplaneMode()) {
+                    + "," + mEnabled + "," + isPhoneStillRegistered() + "," + mDelay
+                    + "," + isGlobalMode() + "," + mSST.isRadioOn());
+            if (!mEnabled || mDelay == UNINITIALIZED_DELAY_VALUE || isPhoneStillRegistered()
+                    || isGlobalMode() || isRadioOffOrAirplaneMode()) {
                 return false;
             }
             return true;
@@ -559,6 +595,22 @@
             return mDelay;
         }
 
+        /**
+         * Checks whether this Notification is enabled.
+         * @return {@code true} if this Notification is enabled, false otherwise
+         */
+        public boolean isEnabled() {
+            return true;
+        }
+
+        /**
+         * Sets whether this Notification is enabled. If disabled, it will not build notification.
+         * @param bundle PersistableBundle
+         */
+        public void setEnabled(PersistableBundle bundle) {
+            // always allowed. There is no config to hide notifications.
+        }
+
         public int getTypeId() {
             return mTypeId;
         }
diff --git a/src/java/com/android/internal/telephony/CellBroadcastConfigTracker.java b/src/java/com/android/internal/telephony/CellBroadcastConfigTracker.java
index 82d4409..7e8663a 100644
--- a/src/java/com/android/internal/telephony/CellBroadcastConfigTracker.java
+++ b/src/java/com/android/internal/telephony/CellBroadcastConfigTracker.java
@@ -26,6 +26,8 @@
 import android.telephony.SmsCbMessage;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
@@ -33,6 +35,8 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -59,6 +63,7 @@
     // Cache of current cell broadcast id ranges of 3gpp2
     private List<CellBroadcastIdRange> mCbRanges3gpp2 = new CopyOnWriteArrayList<>();
     private Phone mPhone;
+    private final LocalLog mLocalLog = new LocalLog(128);
     @VisibleForTesting
     public int mSubId;
     @VisibleForTesting
@@ -106,8 +111,7 @@
         @Override
         public String toString() {
             return "Request[mCbRangesRequest3gpp = " + mCbRangesRequest3gpp + ", "
-                    + "mCbRangesRequest3gpp2 = " + mCbRangesRequest3gpp2 + ", "
-                    + "mCallback = " + mCallback + "]";
+                    + "mCbRangesRequest3gpp2 = " + mCbRangesRequest3gpp2 + "]";
         }
     }
 
@@ -175,6 +179,9 @@
                     Request request = (Request) msg.obj;
                     if (DBG) {
                         logd("IdleState handle EVENT_REQUEST with request:" + request);
+                        mLocalLog.log("IdleState handle EVENT_REQUEST with request:" + request
+                                + ", mCbRanges3gpp:" + mCbRanges3gpp
+                                + ", mCbRanges3gpp2:" + mCbRanges3gpp2);
                     }
                     if (!mCbRanges3gpp.equals(request.get3gppRanges())) {
                         // set gsm config if the config is changed
@@ -229,6 +236,7 @@
                         transitionTo(mGsmActivatingState);
                     } else {
                         logd("Failed to set gsm config");
+                        mLocalLog.log("GsmConfiguringState Failed to set gsm config:" + request);
                         request.getCallback().accept(
                                 TelephonyManager.CELL_BROADCAST_RESULT_FAIL_CONFIG);
                         // transit to idle state on the failure case
@@ -265,6 +273,8 @@
                     if (DBG) {
                         logd("GsmActivatingState handle EVENT_ACTIVATION_DONE with request:"
                                 + request);
+                        mLocalLog.log("GsmActivatingState EVENT_ACTIVATION_DONE, exception:"
+                                + ar.exception + ", request:" + request);
                     }
                     if (ar.exception == null) {
                         mCbRanges3gpp = request.get3gppRanges();
@@ -326,6 +336,7 @@
                         transitionTo(mCdmaActivatingState);
                     } else {
                         logd("Failed to set cdma config");
+                        mLocalLog.log("CdmaConfiguringState Failed to set cdma config:" + request);
                         request.getCallback().accept(
                                 TelephonyManager.CELL_BROADCAST_RESULT_FAIL_CONFIG);
                         // transit to idle state on the failure case
@@ -362,6 +373,8 @@
                     if (DBG) {
                         logd("CdmaActivatingState handle EVENT_ACTIVATION_DONE with request:"
                                 + request);
+                        mLocalLog.log("CdmaActivatingState EVENT_ACTIVATION_DONE, exception:"
+                                + ar.exception + ", request:" + request);
                     }
                     if (ar.exception == null) {
                         mCbRanges3gpp2 = request.get3gpp2Ranges();
@@ -531,4 +544,26 @@
             mPhone.mCi.setCdmaBroadcastActivation(activate, response);
         }
     }
+
+    /**
+     * Dump the state of CellBroadcastConfigTracker
+     *
+     * @param fd File descriptor
+     * @param printWriter Print writer
+     * @param args Arguments
+     */
+    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
+        pw.println(CellBroadcastConfigTracker.class.getSimpleName()
+                + "-" + mPhone.getPhoneId() + ":");
+        pw.increaseIndent();
+        pw.println("Current mCbRanges3gpp:" + mCbRanges3gpp);
+        pw.println("Current mCbRanges3gpp2:" + mCbRanges3gpp2);
+        pw.decreaseIndent();
+
+        pw.println("Local logs:");
+        pw.increaseIndent();
+        mLocalLog.dump(fd, pw, args);
+        pw.decreaseIndent();
+    }
 }
diff --git a/src/java/com/android/internal/telephony/GbaManager.java b/src/java/com/android/internal/telephony/GbaManager.java
index b1db1ac..7c5f636 100644
--- a/src/java/com/android/internal/telephony/GbaManager.java
+++ b/src/java/com/android/internal/telephony/GbaManager.java
@@ -40,6 +40,7 @@
 import com.android.internal.telephony.metrics.RcsStats;
 import com.android.telephony.Rlog;
 
+import java.util.NoSuchElementException;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 /**
@@ -155,7 +156,11 @@
 
         public synchronized void unlinkToDeath() {
             if (mBinder != null) {
-                mBinder.unlinkToDeath(this, 0);
+                try {
+                    mBinder.unlinkToDeath(this, 0);
+                } catch (NoSuchElementException e) {
+                    // do nothing
+                }
                 mBinder = null;
             }
         }
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 5eae061..6e2601e 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -249,6 +249,7 @@
     private String mImeiSv;
     private String mVmNumber;
     private int mImeiType = IMEI_TYPE_UNKNOWN;
+    private int mSimState = TelephonyManager.SIM_STATE_UNKNOWN;
 
     @VisibleForTesting
     public CellBroadcastConfigTracker mCellBroadcastConfigTracker =
@@ -426,9 +427,9 @@
                 if (mPhoneId == intent.getIntExtra(
                         SubscriptionManager.EXTRA_SLOT_INDEX,
                         SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
-                    int simState = intent.getIntExtra(TelephonyManager.EXTRA_SIM_STATE,
+                    mSimState = intent.getIntExtra(TelephonyManager.EXTRA_SIM_STATE,
                             TelephonyManager.SIM_STATE_UNKNOWN);
-                    if (simState == TelephonyManager.SIM_STATE_LOADED
+                    if (mSimState == TelephonyManager.SIM_STATE_LOADED
                             && currentSlotSubIdChanged()) {
                         setNetworkSelectionModeAutomatic(null);
                     }
@@ -680,6 +681,7 @@
         mTelecomVoiceServiceStateOverride = newOverride;
         if (changed && mSST != null) {
             mSST.onTelecomVoiceServiceStateOverrideChanged();
+            mSST.getServiceStateStats().onVoiceServiceStateOverrideChanged(hasService);
         }
     }
 
@@ -3521,13 +3523,14 @@
             case EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE:
                 logd("EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE");
                 ar = (AsyncResult) msg.obj;
+                // Only test for a success here in order to flip the support flag.
+                // Testing for the negative case, e.g. REQUEST_NOT_SUPPORTED, is insufficient
+                // because the modem or the RIL could still return exceptions for temporary
+                // failures even when the feature is unsupported.
                 if (ar == null || ar.exception == null) {
                     mIsNullCipherAndIntegritySupported = true;
                     return;
                 }
-                CommandException.Error error = ((CommandException) ar.exception).getCommandError();
-                mIsNullCipherAndIntegritySupported = !error.equals(
-                        CommandException.Error.REQUEST_NOT_SUPPORTED);
                 break;
 
             case EVENT_IMS_DEREGISTRATION_TRIGGERED:
@@ -4499,6 +4502,12 @@
             e.printStackTrace();
         }
         pw.flush();
+        try {
+            mCellBroadcastConfigTracker.dump(fd, pw, args);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        pw.flush();
     }
 
     @Override
@@ -5017,6 +5026,10 @@
             return;
         }
 
+        if (mSimState != TelephonyManager.SIM_STATE_LOADED) {
+            return;
+        }
+
         if (config == null) {
             loge("didn't get the vonr_enabled_bool from the carrier config.");
             return;
diff --git a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
index 90885fe..9b116b4 100644
--- a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
+++ b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
@@ -657,7 +657,6 @@
 
     @VisibleForTesting
     public void fallbackToPstn(SmsTracker tracker) {
-        tracker.mMessageRef = nextMessageRef();
         mSmsDispatchersController.sendRetrySms(tracker);
     }
 
diff --git a/src/java/com/android/internal/telephony/LocaleTracker.java b/src/java/com/android/internal/telephony/LocaleTracker.java
index de854fa..076b001 100644
--- a/src/java/com/android/internal/telephony/LocaleTracker.java
+++ b/src/java/com/android/internal/telephony/LocaleTracker.java
@@ -372,7 +372,10 @@
      */
     public void updateOperatorNumeric(String operatorNumeric) {
         if (TextUtils.isEmpty(operatorNumeric)) {
-            sendMessageDelayed(obtainMessage(EVENT_OPERATOR_LOST), SERVICE_OPERATOR_LOST_DELAY_MS);
+            if (!hasMessages(EVENT_OPERATOR_LOST)) {
+                sendMessageDelayed(obtainMessage(EVENT_OPERATOR_LOST),
+                        SERVICE_OPERATOR_LOST_DELAY_MS);
+            }
         } else {
             removeMessages(EVENT_OPERATOR_LOST);
             updateOperatorNumericImmediate(operatorNumeric);
@@ -528,6 +531,9 @@
         if (!mPhone.isRadioOn()) {
             countryIso = "";
             countryIsoDebugInfo = "radio off";
+
+            // clear cell infos, we don't know where the next network to camp on.
+            mCellInfoList = null;
         }
 
         log("updateLocale: countryIso = " + countryIso
diff --git a/src/java/com/android/internal/telephony/MultiSimSettingController.java b/src/java/com/android/internal/telephony/MultiSimSettingController.java
index 0acae4b..d6b0930 100644
--- a/src/java/com/android/internal/telephony/MultiSimSettingController.java
+++ b/src/java/com/android/internal/telephony/MultiSimSettingController.java
@@ -83,7 +83,8 @@
     private static final int EVENT_SUBSCRIPTION_INFO_CHANGED         = 4;
     private static final int EVENT_SUBSCRIPTION_GROUP_CHANGED        = 5;
     private static final int EVENT_DEFAULT_DATA_SUBSCRIPTION_CHANGED = 6;
-    private static final int EVENT_MULTI_SIM_CONFIG_CHANGED          = 8;
+    @VisibleForTesting
+    public static final int EVENT_MULTI_SIM_CONFIG_CHANGED          = 8;
     @VisibleForTesting
     public static final int EVENT_RADIO_STATE_CHANGED                = 9;
 
@@ -150,6 +151,8 @@
 
     // The number of existing DataSettingsControllerCallback
     private int mCallbacksCount;
+    /** The number of active modem count. */
+    private int mActiveModemCount;
 
     private static final String SETTING_USER_PREF_DATA_SUB = "user_preferred_data_sub";
 
@@ -163,15 +166,13 @@
         }
 
         @Override
-        public void onDataEnabledChanged(boolean enabled,
-                @TelephonyManager.DataEnabledChangedReason int reason, String callingPackage) {
+        public void onUserDataEnabledChanged(boolean enabled, @NonNull String callingPackage) {
             int subId = mPhone.getSubId();
-            // notifyUserDataEnabled if the change is called from external and reason is
-            // DATA_ENABLED_REASON_USER
+            // only notifyUserDataEnabled if the change is called from external to avoid
+            // setUserDataEnabledForGroup infinite loop
             if (SubscriptionManager.isValidSubscriptionId(subId)
-                    && reason == TelephonyManager.DATA_ENABLED_REASON_USER
                     && !getInstance().mContext.getOpPackageName().equals(callingPackage)) {
-                getInstance().notifyUserDataEnabled(mPhone.getSubId(), enabled);
+                getInstance().notifyUserDataEnabled(subId, enabled);
             }
         }
 
@@ -217,11 +218,14 @@
         mSubscriptionManagerService = SubscriptionManagerService.getInstance();
 
         // Initialize mCarrierConfigLoadedSubIds and register to listen to carrier config change.
-        final int phoneCount = ((TelephonyManager) mContext.getSystemService(
-                Context.TELEPHONY_SERVICE)).getSupportedModemCount();
+        TelephonyManager telephonyManager = ((TelephonyManager) mContext.getSystemService(
+                TelephonyManager.class));
+        final int phoneCount = telephonyManager.getSupportedModemCount();
         mCarrierConfigLoadedSubIds = new int[phoneCount];
         Arrays.fill(mCarrierConfigLoadedSubIds, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
 
+        mActiveModemCount = telephonyManager.getActiveModemCount();
+
         PhoneConfigurationManager.registerForMultiSimConfigChange(
                 this, EVENT_MULTI_SIM_CONFIG_CHANGED, null);
 
@@ -316,9 +320,14 @@
                 onMultiSimConfigChanged(activeModems);
                 break;
             case EVENT_RADIO_STATE_CHANGED:
-                for (Phone phone : PhoneFactory.getPhones()) {
-                    if (phone.mCi.getRadioState() == TelephonyManager.RADIO_POWER_UNAVAILABLE) {
-                        if (DBG) log("Radio unavailable. Clearing sub info initialized flag.");
+                for (int phoneId = 0; phoneId < mActiveModemCount; phoneId++) {
+                    Phone phone = PhoneFactory.getPhone(phoneId);
+                    if (phone != null && phone.mCi.getRadioState()
+                            == TelephonyManager.RADIO_POWER_UNAVAILABLE) {
+                        if (DBG) {
+                            log("Radio unavailable on phone " + phoneId
+                                    + ", clearing sub info initialized flag");
+                        }
                         mSubInfoInitialized = false;
                         break;
                     }
@@ -453,6 +462,8 @@
     }
 
     private void onMultiSimConfigChanged(int activeModems) {
+        mActiveModemCount = activeModems;
+        log("onMultiSimConfigChanged: current ActiveModemCount=" + mActiveModemCount);
         // Clear mCarrierConfigLoadedSubIds. Other actions will responds to active
         // subscription change.
         for (int phoneId = activeModems; phoneId < mCarrierConfigLoadedSubIds.length; phoneId++) {
@@ -601,8 +612,7 @@
         // opportunistic subscription active (activeSubInfos.size() > 1), we automatically
         // set the primary to be default SIM and return.
         if (mPrimarySubList.size() == 1 && (change != PRIMARY_SUB_REMOVED
-                || ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
-                .getActiveModemCount() == 1)) {
+                || mActiveModemCount == 1)) {
             int subId = mPrimarySubList.get(0);
             if (DBG) log("updateDefaultValues: to only primary sub " + subId);
             mSubscriptionManagerService.setDefaultDataSubId(subId);
@@ -1058,10 +1068,11 @@
     }
 
     private boolean isRadioAvailableOnAllSubs() {
-        for (Phone phone : PhoneFactory.getPhones()) {
-            if ((phone.mCi != null &&
-                    phone.mCi.getRadioState() == TelephonyManager.RADIO_POWER_UNAVAILABLE) ||
-                    phone.isShuttingDown()) {
+        for (int phoneId = 0; phoneId < mActiveModemCount; phoneId++) {
+            Phone phone = PhoneFactory.getPhone(phoneId);
+            if (phone != null
+                    && (phone.mCi.getRadioState() == TelephonyManager.RADIO_POWER_UNAVAILABLE
+                    || phone.isShuttingDown())) {
                 return false;
             }
         }
diff --git a/src/java/com/android/internal/telephony/NetworkTypeController.java b/src/java/com/android/internal/telephony/NetworkTypeController.java
index beebf22..d1c8359 100644
--- a/src/java/com/android/internal/telephony/NetworkTypeController.java
+++ b/src/java/com/android/internal/telephony/NetworkTypeController.java
@@ -29,6 +29,7 @@
 import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation;
 import android.telephony.CarrierConfigManager;
+import android.telephony.CellInfo;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.ServiceState;
@@ -40,7 +41,6 @@
 
 import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
 import com.android.internal.telephony.data.DataUtils;
-import com.android.internal.telephony.util.ArrayUtils;
 import com.android.internal.util.IState;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.State;
@@ -52,9 +52,11 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.IntStream;
@@ -105,7 +107,7 @@
     /** Event for preferred network mode changed. */
     private static final int EVENT_PREFERRED_NETWORK_MODE_CHANGED = 10;
     /** Event for physical channel configs changed. */
-    private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED = 11;
+    private static final int EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED = 11;
     /** Event for device idle mode changed, when device goes to deep sleep and pauses all timers. */
     private static final int EVENT_DEVICE_IDLE_MODE_CHANGED = 12;
 
@@ -123,13 +125,13 @@
         sEvents[EVENT_RADIO_OFF_OR_UNAVAILABLE] = "EVENT_RADIO_OFF_OR_UNAVAILABLE";
         sEvents[EVENT_PREFERRED_NETWORK_MODE_CHANGED] = "EVENT_PREFERRED_NETWORK_MODE_CHANGED";
         sEvents[EVENT_INITIALIZE] = "EVENT_INITIALIZE";
-        sEvents[EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED] = "EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED";
+        sEvents[EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED] = "EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED";
         sEvents[EVENT_DEVICE_IDLE_MODE_CHANGED] = "EVENT_DEVICE_IDLE_MODE_CHANGED";
     }
 
-    private final @NonNull Phone mPhone;
-    private final @NonNull DisplayInfoController mDisplayInfoController;
-    private final @NonNull BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+    @NonNull private final Phone mPhone;
+    @NonNull private final DisplayInfoController mDisplayInfoController;
+    @NonNull private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             switch (intent.getAction()) {
@@ -140,7 +142,7 @@
         }
     };
 
-    private final @NonNull CarrierConfigManager.CarrierConfigChangeListener
+    @NonNull private final CarrierConfigManager.CarrierConfigChangeListener
             mCarrierConfigChangeListener =
             new CarrierConfigManager.CarrierConfigChangeListener() {
                 @Override
@@ -153,9 +155,9 @@
                 }
             };
 
-    private @NonNull Map<String, OverrideTimerRule> mOverrideTimerRules = new HashMap<>();
-    private @NonNull String mLteEnhancedPattern = "";
-    private @Annotation.OverrideNetworkType int mOverrideNetworkType;
+    @NonNull private Map<String, OverrideTimerRule> mOverrideTimerRules = new HashMap<>();
+    @NonNull private String mLteEnhancedPattern = "";
+    @Annotation.OverrideNetworkType private int mOverrideNetworkType;
     private boolean mIsPhysicalChannelConfigOn;
     private boolean mIsPrimaryTimerActive;
     private boolean mIsSecondaryTimerActive;
@@ -163,11 +165,12 @@
     private int mLtePlusThresholdBandwidth;
     private int mNrAdvancedThresholdBandwidth;
     private boolean mIncludeLteForNrAdvancedThresholdBandwidth;
-    private @NonNull int[] mAdditionalNrAdvancedBandsList;
-    private @NonNull String mPrimaryTimerState;
-    private @NonNull String mSecondaryTimerState;
-    private @NonNull String mPreviousState;
-    private @LinkStatus int mPhysicalLinkStatus;
+    private boolean mRatchetPccFieldsForSameAnchorNrCell;
+    @NonNull private final Set<Integer> mAdditionalNrAdvancedBands = new HashSet<>();
+    @NonNull private String mPrimaryTimerState;
+    @NonNull private String mSecondaryTimerState;
+    @NonNull private String mPreviousState;
+    @LinkStatus private int mPhysicalLinkStatus;
     private boolean mIsPhysicalChannelConfig16Supported;
     private boolean mIsNrAdvancedAllowedByPco = false;
     private int mNrAdvancedCapablePcoId = 0;
@@ -175,12 +178,17 @@
     private boolean mEnableNrAdvancedWhileRoaming = true;
     private boolean mIsDeviceIdleMode = false;
 
-    private @Nullable DataNetworkControllerCallback mNrAdvancedCapableByPcoChangedCallback = null;
-    private @Nullable DataNetworkControllerCallback mNrPhysicalLinkStatusChangedCallback = null;
+    @Nullable private DataNetworkControllerCallback mNrAdvancedCapableByPcoChangedCallback = null;
+    @Nullable private DataNetworkControllerCallback mNrPhysicalLinkStatusChangedCallback = null;
 
     // Cached copies below to prevent race conditions
-    private @NonNull ServiceState mServiceState;
-    private @Nullable List<PhysicalChannelConfig> mPhysicalChannelConfigs;
+    @NonNull private ServiceState mServiceState;
+    @Nullable private List<PhysicalChannelConfig> mPhysicalChannelConfigs;
+
+    // Ratchet physical channel config fields to prevent 5G/5G+ flickering
+    @NonNull private Set<Integer> mRatchetedNrBands = new HashSet<>();
+    private int mRatchetedNrBandwidths = 0;
+    private int mLastAnchorNrCellId = PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN;
 
     /**
      * NetworkTypeController constructor.
@@ -246,7 +254,7 @@
         mPhone.registerForPreferredNetworkTypeChanged(getHandler(),
                 EVENT_PREFERRED_NETWORK_MODE_CHANGED, null);
         mPhone.registerForPhysicalChannelConfig(getHandler(),
-                EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED, null);
+                EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED, null);
         mPhone.getServiceStateTracker().registerForServiceStateChanged(getHandler(),
                 EVENT_SERVICE_STATE_CHANGED, null);
         mIsPhysicalChannelConfig16Supported = mPhone.getContext().getSystemService(
@@ -293,10 +301,16 @@
                 CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT);
         mIncludeLteForNrAdvancedThresholdBandwidth = config.getBoolean(
                 CarrierConfigManager.KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL);
+        mRatchetPccFieldsForSameAnchorNrCell = config.getBoolean(
+                CarrierConfigManager.KEY_RATCHET_NR_ADVANCED_BANDWIDTH_IF_RRC_IDLE_BOOL);
         mEnableNrAdvancedWhileRoaming = config.getBoolean(
                 CarrierConfigManager.KEY_ENABLE_NR_ADVANCED_WHILE_ROAMING_BOOL);
-        mAdditionalNrAdvancedBandsList = config.getIntArray(
+        mAdditionalNrAdvancedBands.clear();
+        int[] additionalNrAdvancedBands = config.getIntArray(
                 CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY);
+        if (additionalNrAdvancedBands != null) {
+            Arrays.stream(additionalNrAdvancedBands).forEach(mAdditionalNrAdvancedBands::add);
+        }
         mNrAdvancedCapablePcoId = config.getInt(
                 CarrierConfigManager.KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT);
         if (mNrAdvancedCapablePcoId > 0 && mNrAdvancedCapableByPcoChangedCallback == null) {
@@ -343,6 +357,7 @@
         String overrideSecondaryTimerRule = config.getString(
                 CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING);
         createTimerRules(nrIconConfiguration, overrideTimerRule, overrideSecondaryTimerRule);
+        updatePhysicalChannelConfigs();
     }
 
     private void createTimerRules(String icons, String timers, String secondaryTimers) {
@@ -553,9 +568,9 @@
                     quit();
                     break;
                 case EVENT_INITIALIZE:
-                    // The reason that we do it here is because some of the works below requires
-                    // other modules (e.g. DataNetworkController, ServiceStateTracker), which is not
-                    // created yet when NetworkTypeController is created.
+                    // The reason that we do it here is that the work below requires other modules
+                    // (e.g. DataNetworkController, ServiceStateTracker), which are not created
+                    // when NetworkTypeController is created.
                     registerForAllEvents();
                     parseCarrierConfigs();
                     break;
@@ -579,6 +594,9 @@
                             log("Reset timers since physical channel config indications are off.");
                         }
                         resetAllTimers();
+                        mRatchetedNrBands.clear();
+                        mRatchetedNrBandwidths = 0;
+                        mLastAnchorNrCellId = PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN;
                     }
                     transitionToCurrentState();
                     break;
@@ -609,10 +627,8 @@
                     resetAllTimers();
                     transitionToCurrentState();
                     break;
-                case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
-                    mPhysicalChannelConfigs =
-                            mPhone.getServiceStateTracker().getPhysicalChannelConfigList();
-                    if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs);
+                case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
+                    updatePhysicalChannelConfigs();
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                     }
@@ -689,10 +705,8 @@
                     }
                     mIsNrRestricted = isNrRestricted();
                     break;
-                case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
-                    mPhysicalChannelConfigs =
-                            mPhone.getServiceStateTracker().getPhysicalChannelConfigList();
-                    if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs);
+                case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
+                    updatePhysicalChannelConfigs();
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                         if (mIsTimerResetEnabledForLegacyStateRrcIdle && !isPhysicalLinkActive()) {
@@ -770,10 +784,8 @@
                         }
                     }
                     break;
-                case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
-                    mPhysicalChannelConfigs =
-                            mPhone.getServiceStateTracker().getPhysicalChannelConfigList();
-                    if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs);
+                case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
+                    updatePhysicalChannelConfigs();
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                         if (isPhysicalLinkActive()) {
@@ -854,10 +866,8 @@
                         }
                     }
                     break;
-                case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
-                    mPhysicalChannelConfigs =
-                            mPhone.getServiceStateTracker().getPhysicalChannelConfigList();
-                    if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs);
+                case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
+                    updatePhysicalChannelConfigs();
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                         if (!isPhysicalLinkActive()) {
@@ -935,10 +945,8 @@
                         transitionWithTimerTo(mLegacyState);
                     }
                     break;
-                case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
-                    mPhysicalChannelConfigs =
-                            mPhone.getServiceStateTracker().getPhysicalChannelConfigList();
-                    if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs);
+                case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
+                    updatePhysicalChannelConfigs();
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                     }
@@ -1016,10 +1024,8 @@
                         transitionWithTimerTo(mLegacyState);
                     }
                     break;
-                case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
-                    mPhysicalChannelConfigs =
-                            mPhone.getServiceStateTracker().getPhysicalChannelConfigList();
-                    if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs);
+                case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
+                    updatePhysicalChannelConfigs();
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                     }
@@ -1050,6 +1056,77 @@
     private final NrConnectedAdvancedState mNrConnectedAdvancedState =
             new NrConnectedAdvancedState();
 
+    private void updatePhysicalChannelConfigs() {
+        List<PhysicalChannelConfig> physicalChannelConfigs =
+                mPhone.getServiceStateTracker().getPhysicalChannelConfigList();
+        boolean isPccListEmpty = physicalChannelConfigs == null || physicalChannelConfigs.isEmpty();
+        if (isPccListEmpty && isUsingPhysicalChannelConfigForRrcDetection()) {
+            log("Physical channel configs updated: not updating PCC fields for empty PCC list "
+                    + "indicating RRC idle.");
+            mPhysicalChannelConfigs = physicalChannelConfigs;
+            return;
+        }
+
+        int anchorNrCellId = PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN;
+        int anchorLteCellId = PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN;
+        int nrBandwidths = 0;
+        Set<Integer> nrBands = new HashSet<>();
+        if (physicalChannelConfigs != null) {
+            for (PhysicalChannelConfig config : physicalChannelConfigs) {
+                if (config.getNetworkType() == TelephonyManager.NETWORK_TYPE_NR) {
+                    if (config.getConnectionStatus() == CellInfo.CONNECTION_PRIMARY_SERVING
+                            && anchorNrCellId == PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN) {
+                        anchorNrCellId = config.getPhysicalCellId();
+                    }
+                    nrBandwidths += config.getCellBandwidthDownlinkKhz();
+                    nrBands.add(config.getBand());
+                } else if (config.getNetworkType() == TelephonyManager.NETWORK_TYPE_LTE) {
+                    if (config.getConnectionStatus() == CellInfo.CONNECTION_PRIMARY_SERVING
+                            && anchorLteCellId == PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN) {
+                        anchorLteCellId = config.getPhysicalCellId();
+                    }
+                    if (mIncludeLteForNrAdvancedThresholdBandwidth) {
+                        nrBandwidths += config.getCellBandwidthDownlinkKhz();
+                    }
+                }
+            }
+        }
+
+        // Update anchor NR cell from anchor LTE cell for NR NSA
+        if (anchorNrCellId == PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN
+                && anchorLteCellId != PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN) {
+            anchorNrCellId = anchorLteCellId;
+        }
+
+        if (anchorNrCellId == PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN) {
+            if (!isPccListEmpty) {
+                log("Ignoring physical channel config fields without an anchor NR cell, "
+                        + "either due to LTE-only configs or an unspecified cell ID.");
+            }
+            mRatchetedNrBandwidths = 0;
+            mRatchetedNrBands.clear();
+        } else if (anchorNrCellId == mLastAnchorNrCellId && mRatchetPccFieldsForSameAnchorNrCell) {
+            log("Ratchet physical channel config fields since anchor NR cell is the same.");
+            mRatchetedNrBandwidths = Math.max(mRatchetedNrBandwidths, nrBandwidths);
+            mRatchetedNrBands.addAll(nrBands);
+        } else {
+            if (mRatchetPccFieldsForSameAnchorNrCell) {
+                log("Not ratcheting physical channel config fields since anchor NR cell changed: "
+                        + mLastAnchorNrCellId + " -> " + anchorNrCellId);
+            }
+            mRatchetedNrBandwidths = nrBandwidths;
+            mRatchetedNrBands = nrBands;
+        }
+
+        mLastAnchorNrCellId = anchorNrCellId;
+        mPhysicalChannelConfigs = physicalChannelConfigs;
+        if (DBG) {
+            log("Physical channel configs updated: anchorNrCell=" + mLastAnchorNrCellId
+                    + ", nrBandwidths=" + mRatchetedNrBandwidths + ", nrBands=" +  mRatchetedNrBands
+                    + ", configs=" + mPhysicalChannelConfigs);
+        }
+    }
+
     private void transitionWithTimerTo(IState destState) {
         String destName = destState.getName();
         if (DBG) log("Transition with primary timer from " + mPreviousState + " to " + destName);
@@ -1275,29 +1352,25 @@
         // Check PCO requirement. For carriers using PCO to indicate whether the data connection is
         // NR advanced capable, mNrAdvancedCapablePcoId should be configured to non-zero.
         if (mNrAdvancedCapablePcoId > 0 && !mIsNrAdvancedAllowedByPco) {
+            if (DBG) log("isNrAdvanced: not allowed by PCO for PCO ID " + mNrAdvancedCapablePcoId);
             return false;
         }
 
         // Check if NR advanced is enabled when the device is roaming. Some carriers disable it
         // while the device is roaming.
         if (mServiceState.getDataRoaming() && !mEnableNrAdvancedWhileRoaming) {
+            if (DBG) log("isNrAdvanced: false because NR advanced is unavailable while roaming.");
             return false;
         }
 
-        int bandwidths = 0;
-        if (mPhone.getServiceStateTracker().getPhysicalChannelConfigList() != null) {
-            bandwidths = mPhone.getServiceStateTracker().getPhysicalChannelConfigList()
-                    .stream()
-                    .filter(config -> mIncludeLteForNrAdvancedThresholdBandwidth
-                            || config.getNetworkType() == TelephonyManager.NETWORK_TYPE_NR)
-                    .map(PhysicalChannelConfig::getCellBandwidthDownlinkKhz)
-                    .mapToInt(Integer::intValue)
-                    .sum();
-        }
-
         // Check if meeting minimum bandwidth requirement. For most carriers, there is no minimum
         // bandwidth requirement and mNrAdvancedThresholdBandwidth is 0.
-        if (mNrAdvancedThresholdBandwidth > 0 && bandwidths < mNrAdvancedThresholdBandwidth) {
+        if (mNrAdvancedThresholdBandwidth > 0
+                && mRatchetedNrBandwidths < mNrAdvancedThresholdBandwidth) {
+            if (DBG) {
+                log("isNrAdvanced: false because bandwidths=" + mRatchetedNrBandwidths
+                        + " does not meet the threshold=" + mNrAdvancedThresholdBandwidth);
+            }
             return false;
         }
 
@@ -1311,17 +1384,17 @@
     }
 
     private boolean isAdditionalNrAdvancedBand() {
-        if (ArrayUtils.isEmpty(mAdditionalNrAdvancedBandsList)
-                || mPhysicalChannelConfigs == null) {
+        if (mAdditionalNrAdvancedBands.isEmpty() || mRatchetedNrBands.isEmpty()) {
+            if (DBG && !mAdditionalNrAdvancedBands.isEmpty()) {
+                // Only log if mAdditionalNrAdvancedBands is empty to prevent log spam
+                log("isAdditionalNrAdvancedBand: false because bands are empty; configs="
+                        + mAdditionalNrAdvancedBands + ", bands=" + mRatchetedNrBands);
+            }
             return false;
         }
-        for (PhysicalChannelConfig item : mPhysicalChannelConfigs) {
-            if (item.getNetworkType() == TelephonyManager.NETWORK_TYPE_NR
-                    && ArrayUtils.contains(mAdditionalNrAdvancedBandsList, item.getBand())) {
-                return true;
-            }
-        }
-        return false;
+        Set<Integer> intersection = new HashSet<>(mAdditionalNrAdvancedBands);
+        intersection.retainAll(mRatchetedNrBands);
+        return !intersection.isEmpty();
     }
 
     private boolean isLte(int rat) {
@@ -1388,8 +1461,13 @@
                 + mIsTimerResetEnabledForLegacyStateRrcIdle);
         pw.println("mLtePlusThresholdBandwidth=" + mLtePlusThresholdBandwidth);
         pw.println("mNrAdvancedThresholdBandwidth=" + mNrAdvancedThresholdBandwidth);
-        pw.println("mAdditionalNrAdvancedBandsList="
-                + Arrays.toString(mAdditionalNrAdvancedBandsList));
+        pw.println("mIncludeLteForNrAdvancedThresholdBandwidth="
+                + mIncludeLteForNrAdvancedThresholdBandwidth);
+        pw.println("mRatchetPccFieldsForSameAnchorNrCell=" + mRatchetPccFieldsForSameAnchorNrCell);
+        pw.println("mRatchetedNrBandwidths=" + mRatchetedNrBandwidths);
+        pw.println("mAdditionalNrAdvancedBandsList=" + mAdditionalNrAdvancedBands);
+        pw.println("mRatchetedNrBands=" + mRatchetedNrBands);
+        pw.println("mLastAnchorNrCellId=" + mLastAnchorNrCellId);
         pw.println("mPrimaryTimerState=" + mPrimaryTimerState);
         pw.println("mSecondaryTimerState=" + mSecondaryTimerState);
         pw.println("mPreviousState=" + mPreviousState);
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 4e62d20..cee3a58 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -2264,7 +2264,7 @@
      *
      * @return Current signal strength as SignalStrength
      */
-    public SignalStrength getSignalStrength() {
+    public @NonNull SignalStrength getSignalStrength() {
         SignalStrengthController ssc = getSignalStrengthController();
         if (ssc == null) {
             return new SignalStrength();
diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
index 8b95824..06ab584 100644
--- a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
+++ b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
@@ -28,10 +28,12 @@
 import android.os.PowerManager;
 import android.os.RegistrantList;
 import android.os.SystemProperties;
+import android.provider.DeviceConfig;
 import android.sysprop.TelephonyProperties;
 import android.telephony.PhoneCapability;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -46,18 +48,21 @@
  * This class manages phone's configuration which defines the potential capability (static) of the
  * phone and its current activated capability (current).
  * It gets and monitors static and current phone capability from the modem; send broadcast
- * if they change, and and sends commands to modem to enable or disable phones.
+ * if they change, and sends commands to modem to enable or disable phones.
  */
 public class PhoneConfigurationManager {
     public static final String DSDA = "dsda";
     public static final String DSDS = "dsds";
     public static final String TSTS = "tsts";
     public static final String SSSS = "";
+    /** DeviceConfig key for whether Virtual DSDA is enabled. */
+    private static final String KEY_ENABLE_VIRTUAL_DSDA = "enable_virtual_dsda";
     private static final String LOG_TAG = "PhoneCfgMgr";
     private static final int EVENT_SWITCH_DSDS_CONFIG_DONE = 100;
     private static final int EVENT_GET_MODEM_STATUS = 101;
     private static final int EVENT_GET_MODEM_STATUS_DONE = 102;
     private static final int EVENT_GET_PHONE_CAPABILITY_DONE = 103;
+    private static final int EVENT_DEVICE_CONFIG_CHANGED = 104;
 
     private static PhoneConfigurationManager sInstance = null;
     private final Context mContext;
@@ -70,6 +75,11 @@
     private final Map<Integer, Boolean> mPhoneStatusMap;
     private MockableInterface mMi = new MockableInterface();
     private TelephonyManager mTelephonyManager;
+    /**
+     * True if 'Virtual DSDA' i.e., in-call IMS connectivity on both subs with only single logical
+     * modem, is enabled.
+     */
+    private boolean mVirtualDsdaEnabled;
     private static final RegistrantList sMultiSimConfigChangeRegistrants = new RegistrantList();
     private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
     private static final String BOOT_ALLOW_MOCK_MODEM_PROPERTY = "ro.boot.radio.allow_mock_modem";
@@ -102,6 +112,16 @@
         mRadioConfig = RadioConfig.getInstance();
         mHandler = new ConfigManagerHandler();
         mPhoneStatusMap = new HashMap<>();
+        mVirtualDsdaEnabled = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_TELEPHONY, KEY_ENABLE_VIRTUAL_DSDA, false);
+        DeviceConfig.addOnPropertiesChangedListener(
+                DeviceConfig.NAMESPACE_TELEPHONY, Runnable::run,
+                properties -> {
+                    if (TextUtils.equals(DeviceConfig.NAMESPACE_TELEPHONY,
+                            properties.getNamespace())) {
+                        mHandler.sendEmptyMessage(EVENT_DEVICE_CONFIG_CHANGED);
+                    }
+                });
 
         notifyCapabilityChanged();
 
@@ -127,10 +147,7 @@
     // If virtual DSDA is enabled for this UE, then updates maxActiveVoiceSubscriptions to 2.
     private PhoneCapability maybeUpdateMaxActiveVoiceSubscriptions(
             final PhoneCapability staticCapability) {
-        boolean enableVirtualDsda = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_enable_virtual_dsda);
-
-        if (staticCapability.getLogicalModemList().size() > 1 && enableVirtualDsda) {
+        if (staticCapability.getLogicalModemList().size() > 1 && mVirtualDsdaEnabled) {
             return new PhoneCapability.Builder(staticCapability)
                     .setMaxActiveVoiceSubscriptions(2)
                     .build();
@@ -197,13 +214,22 @@
                     ar = (AsyncResult) msg.obj;
                     if (ar != null && ar.exception == null) {
                         mStaticCapability = (PhoneCapability) ar.result;
-                        mStaticCapability =
-                                maybeUpdateMaxActiveVoiceSubscriptions(mStaticCapability);
                         notifyCapabilityChanged();
                     } else {
                         log(msg.what + " failure. Not getting phone capability." + ar.exception);
                     }
                     break;
+                case EVENT_DEVICE_CONFIG_CHANGED:
+                    boolean isVirtualDsdaEnabled = DeviceConfig.getBoolean(
+                            DeviceConfig.NAMESPACE_TELEPHONY, KEY_ENABLE_VIRTUAL_DSDA, false);
+                    if (isVirtualDsdaEnabled != mVirtualDsdaEnabled) {
+                        log("EVENT_DEVICE_CONFIG_CHANGED: from " + mVirtualDsdaEnabled + " to "
+                                + isVirtualDsdaEnabled);
+                        mVirtualDsdaEnabled = isVirtualDsdaEnabled;
+                    }
+                    break;
+                default:
+                    log("Unknown event: " + msg.what);
             }
         }
     }
@@ -317,6 +343,7 @@
                     mHandler, EVENT_GET_PHONE_CAPABILITY_DONE);
             mRadioConfig.getPhoneCapability(callback);
         }
+        mStaticCapability = maybeUpdateMaxActiveVoiceSubscriptions(mStaticCapability);
         log("getStaticPhoneCapability: mStaticCapability " + mStaticCapability);
         return mStaticCapability;
     }
diff --git a/src/java/com/android/internal/telephony/RILUtils.java b/src/java/com/android/internal/telephony/RILUtils.java
index 9db186f..ae8d033 100644
--- a/src/java/com/android/internal/telephony/RILUtils.java
+++ b/src/java/com/android/internal/telephony/RILUtils.java
@@ -288,6 +288,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.radio.data.SliceInfo;
 import android.net.InetAddresses;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
@@ -989,8 +990,9 @@
      * @param dp Data profile
      * @return The converted DataProfileInfo
      */
-    public static android.hardware.radio.data.DataProfileInfo convertToHalDataProfile(
+    public static android.hardware.radio.data.DataProfileInfo convertToHalDataProfile(@Nullable
             DataProfile dp) {
+        if (dp == null) return null;
         android.hardware.radio.data.DataProfileInfo dpi =
                 new android.hardware.radio.data.DataProfileInfo();
 
@@ -3722,7 +3724,8 @@
     private static NetworkSliceInfo convertHalSliceInfo(android.hardware.radio.V1_6.SliceInfo si) {
         NetworkSliceInfo.Builder builder = new NetworkSliceInfo.Builder()
                 .setSliceServiceType(si.sst)
-                .setMappedHplmnSliceServiceType(si.mappedHplmnSst);
+                .setMappedHplmnSliceServiceType(si.mappedHplmnSst)
+                .setStatus(convertHalSliceStatus(si.status));
         if (si.sliceDifferentiator != NetworkSliceInfo.SLICE_DIFFERENTIATOR_NO_SLICE) {
             builder.setSliceDifferentiator(si.sliceDifferentiator)
                     .setMappedHplmnSliceDifferentiator(si.mappedHplmnSD);
@@ -3733,7 +3736,8 @@
     private static NetworkSliceInfo convertHalSliceInfo(android.hardware.radio.data.SliceInfo si) {
         NetworkSliceInfo.Builder builder = new NetworkSliceInfo.Builder()
                 .setSliceServiceType(si.sliceServiceType)
-                .setMappedHplmnSliceServiceType(si.mappedHplmnSst);
+                .setMappedHplmnSliceServiceType(si.mappedHplmnSst)
+                .setStatus(convertHalSliceStatus(si.status));
         if (si.sliceDifferentiator != NetworkSliceInfo.SLICE_DIFFERENTIATOR_NO_SLICE) {
             builder.setSliceDifferentiator(si.sliceDifferentiator)
                     .setMappedHplmnSliceDifferentiator(si.mappedHplmnSd);
@@ -3741,6 +3745,23 @@
         return builder.build();
     }
 
+    @NetworkSliceInfo.SliceStatus private static int convertHalSliceStatus(byte status) {
+        switch (status) {
+            case SliceInfo.STATUS_CONFIGURED:
+                return NetworkSliceInfo.SLICE_STATUS_CONFIGURED;
+            case SliceInfo.STATUS_ALLOWED:
+                return NetworkSliceInfo.SLICE_STATUS_ALLOWED;
+            case SliceInfo.STATUS_REJECTED_NOT_AVAILABLE_IN_PLMN:
+                return NetworkSliceInfo.SLICE_STATUS_REJECTED_NOT_AVAILABLE_IN_PLMN;
+            case SliceInfo.STATUS_REJECTED_NOT_AVAILABLE_IN_REG_AREA:
+                return NetworkSliceInfo.SLICE_STATUS_REJECTED_NOT_AVAILABLE_IN_REGISTERED_AREA;
+            case SliceInfo.STATUS_DEFAULT_CONFIGURED:
+                return NetworkSliceInfo.SLICE_STATUS_DEFAULT_CONFIGURED;
+            default:
+                return NetworkSliceInfo.SLICE_STATUS_UNKNOWN;
+        }
+    }
+
     private static TrafficDescriptor convertHalTrafficDescriptor(
             android.hardware.radio.V1_6.TrafficDescriptor td) throws IllegalArgumentException {
         String dnn = td.dnn.getDiscriminator()
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index 50eea7f..8a49670 100644
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -97,6 +97,7 @@
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.metrics.ServiceStateStats;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
+import com.android.internal.telephony.satellite.NtnCapabilityResolver;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
@@ -404,6 +405,11 @@
                     // state in case our service state was never broadcasted (we don't notify
                     // service states when the subId is invalid)
                     mPhone.notifyServiceStateChanged(mPhone.getServiceState());
+                    // On SubscriptionId changed from invalid  to valid sub id, create
+                    // ServiceStateProvider with valid sub id entry. Note: PollStateDone can update
+                    // the DB again,for the SubID with any change detected at poll state request
+                    log("Update SS information on moving from invalid to valid sub id");
+                    updateServiceStateToDb(mPhone.getServiceState());
                 }
 
                 boolean restoreSelection = !context.getResources().getBoolean(
@@ -2932,8 +2938,8 @@
 
                 // Force display no service
                 final boolean forceDisplayNoService = shouldForceDisplayNoService() && !mIsSimReady;
-                if (!forceDisplayNoService && Phone.isEmergencyCallOnly()) {
-                    // No service but emergency call allowed
+                if (!forceDisplayNoService && (mEmergencyOnly || Phone.isEmergencyCallOnly())) {
+                    // The slot is emc only or the slot is masked as oos due to device is emc only
                     plmn = Resources.getSystem()
                             .getText(com.android.internal.R.string.emergency_calls_only).toString();
                 } else {
@@ -3419,6 +3425,7 @@
 
         updateNrFrequencyRangeFromPhysicalChannelConfigs(mLastPhysicalChannelConfigList, mNewSS);
         updateNrStateFromPhysicalChannelConfigs(mLastPhysicalChannelConfigList, mNewSS);
+        updateNtnCapability();
 
         if (TelephonyUtils.IS_DEBUGGABLE && mPhone.getTelephonyTester() != null) {
             mPhone.getTelephonyTester().overrideServiceState(mNewSS);
@@ -3759,10 +3766,7 @@
                 mPhone.notifyServiceStateChanged(mPhone.getServiceState());
             }
 
-            // insert into ServiceStateProvider. This will trigger apps to wake through JobScheduler
-            mPhone.getContext().getContentResolver()
-                    .insert(getUriForSubscriptionId(mPhone.getSubId()),
-                            getContentValuesForServiceState(mSS));
+            updateServiceStateToDb(mPhone.getServiceState());
 
             TelephonyMetrics.getInstance().writeServiceStateChanged(mPhone.getPhoneId(), mSS);
             mPhone.getVoiceCallSessionStats().onServiceStateChanged(mSS);
@@ -3892,6 +3896,16 @@
         }
     }
 
+    /**
+     * Insert SS information into ServiceStateProvider DB table for a sub id.
+     * This will trigger apps to wake through JobScheduler
+     */
+    private void updateServiceStateToDb(ServiceState serviceState) {
+        mPhone.getContext().getContentResolver()
+                .insert(getUriForSubscriptionId(mPhone.getSubId()),
+                        getContentValuesForServiceState(serviceState));
+    }
+
     private String getOperatorNameFromEri() {
         String eriText = null;
         if (mPhone.isPhoneTypeCdma()) {
@@ -5538,6 +5552,17 @@
         }
     }
 
+    private void updateNtnCapability() {
+        for (NetworkRegistrationInfo nri : mNewSS.getNetworkRegistrationInfoListForTransportType(
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN)) {
+            NtnCapabilityResolver.resolveNtnCapability(nri, mSubId);
+            if (nri.isNonTerrestrialNetwork()) {
+                // Replace the existing NRI with the updated NRI.
+                mNewSS.addNetworkRegistrationInfo(nri);
+            }
+        }
+    }
+
     /**
      * Check if device is non-roaming and always on home network.
      *
diff --git a/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java b/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
index 0ae1b5c..4b5eebc 100644
--- a/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
+++ b/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
@@ -452,10 +452,12 @@
         boolean forceDisplayNoService =
                 mPhone.getServiceStateTracker().shouldForceDisplayNoService() && !isSimReady;
         ServiceState ss = getServiceState();
+        // The slot is emc only or oos but the device is emc only.
+        boolean isEmcOnly = ss.isEmergencyOnly() || Phone.isEmergencyCallOnly();
         if (ss.getState() == ServiceState.STATE_POWER_OFF && !forceDisplayNoService
-                && !Phone.isEmergencyCallOnly()) {
+                && !isEmcOnly) {
             plmn = null;
-        } else if (forceDisplayNoService || !Phone.isEmergencyCallOnly()) {
+        } else if (forceDisplayNoService || !isEmcOnly) {
             plmn = mContext.getResources().getString(
                     com.android.internal.R.string.lockscreen_carrier_default);
         } else {
diff --git a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
new file mode 100644
index 0000000..87591de
--- /dev/null
+++ b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
@@ -0,0 +1,702 @@
+/*
+ * Copyright (C) 2023 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.internal.telephony.data;
+
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.telephony.SubscriptionManager.DEFAULT_PHONE_INDEX;
+import static android.telephony.SubscriptionManager.INVALID_PHONE_INDEX;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.net.NetworkCapabilities;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.Settings;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.SignalStrength;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyDisplayInfo;
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
+import com.android.internal.telephony.util.NotificationChannelController;
+import com.android.telephony.Rlog;
+
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/**
+ * Recommend a data phone to use based on its availability.
+ */
+public class AutoDataSwitchController extends Handler {
+    /** Registration state changed. */
+    public static final int EVALUATION_REASON_REGISTRATION_STATE_CHANGED = 1;
+    /** Telephony Display Info changed. */
+    public static final int EVALUATION_REASON_DISPLAY_INFO_CHANGED = 2;
+    /** Signal Strength changed. */
+    public static final int EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED = 3;
+    /** Default network capabilities changed or lost. */
+    public static final int EVALUATION_REASON_DEFAULT_NETWORK_CHANGED = 4;
+    /** Data enabled settings changed. */
+    public static final int EVALUATION_REASON_DATA_SETTINGS_CHANGED = 5;
+    /** Retry due to previous validation failed. */
+    public static final int EVALUATION_REASON_RETRY_VALIDATION = 6;
+    /** Sim loaded which means slot mapping became available. */
+    public static final int EVALUATION_REASON_SIM_LOADED = 7;
+    /** Voice call ended. */
+    public static final int EVALUATION_REASON_VOICE_CALL_END = 8;
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "EVALUATION_REASON_",
+            value = {EVALUATION_REASON_REGISTRATION_STATE_CHANGED,
+                    EVALUATION_REASON_DISPLAY_INFO_CHANGED,
+                    EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED,
+                    EVALUATION_REASON_DEFAULT_NETWORK_CHANGED,
+                    EVALUATION_REASON_DATA_SETTINGS_CHANGED,
+                    EVALUATION_REASON_RETRY_VALIDATION,
+                    EVALUATION_REASON_SIM_LOADED,
+                    EVALUATION_REASON_VOICE_CALL_END})
+    public @interface AutoDataSwitchEvaluationReason {}
+
+    private static final String LOG_TAG = "ADSC";
+
+    /** Event for service state changed. */
+    private static final int EVENT_SERVICE_STATE_CHANGED = 1;
+    /** Event for display info changed. This is for getting 5G NSA or mmwave information. */
+    private static final int EVENT_DISPLAY_INFO_CHANGED = 2;
+    /** Event for evaluate auto data switch opportunity. */
+    private static final int EVENT_EVALUATE_AUTO_SWITCH = 3;
+    /** Event for signal strength changed. */
+    private static final int EVENT_SIGNAL_STRENGTH_CHANGED = 4;
+    /** Event indicates the switch state is stable, proceed to validation as the next step. */
+    private static final int EVENT_MEETS_AUTO_DATA_SWITCH_STATE = 5;
+
+    /** Fragment "key" argument passed thru {@link #SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS} */
+    private static final String SETTINGS_EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
+    /**
+     * When starting this activity, this extra can also be specified to supply a Bundle of arguments
+     * to pass to that fragment when it is instantiated during the initial creation of the activity.
+     */
+    private static final String SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS =
+            ":settings:show_fragment_args";
+    /** The resource ID of the auto data switch fragment in settings. **/
+    private static final String AUTO_DATA_SWITCH_SETTING_R_ID = "auto_data_switch";
+    /** Notification tag **/
+    private static final String AUTO_DATA_SWITCH_NOTIFICATION_TAG = "auto_data_switch";
+    /** Notification ID **/
+    private static final int AUTO_DATA_SWITCH_NOTIFICATION_ID = 1;
+
+    private final @NonNull LocalLog mLocalLog = new LocalLog(128);
+    private final @NonNull Context mContext;
+    private final @NonNull SubscriptionManagerService mSubscriptionManagerService;
+    private final @NonNull PhoneSwitcher mPhoneSwitcher;
+    private final @NonNull AutoDataSwitchControllerCallback mPhoneSwitcherCallback;
+    private boolean mDefaultNetworkIsOnNonCellular = false;
+    /** {@code true} if we've displayed the notification the first time auto switch occurs **/
+    private boolean mDisplayedNotification = false;
+    /**
+     * Time threshold in ms to define a internet connection status to be stable(e.g. out of service,
+     * in service, wifi is the default active network.etc), while -1 indicates auto switch
+     * feature disabled.
+     */
+    private long mAutoDataSwitchAvailabilityStabilityTimeThreshold = -1;
+    /**
+     * {@code true} if requires ping test before switching preferred data modem; otherwise, switch
+     * even if ping test fails.
+     */
+    private boolean mRequirePingTestBeforeSwitch = true;
+    /** The count of consecutive auto switch validation failure **/
+    private int mAutoSwitchValidationFailedCount = 0;
+    /**
+     * The maximum number of retries when a validation for switching failed.
+     */
+    private int mAutoDataSwitchValidationMaxRetry;
+
+    private @NonNull PhoneSignalStatus[] mPhonesSignalStatus;
+
+    /**
+     * To track the signal status of a phone in order to evaluate whether it's a good candidate to
+     * switch to.
+     */
+    private static class PhoneSignalStatus {
+        private @NonNull Phone mPhone;
+        private @NetworkRegistrationInfo.RegistrationState int mDataRegState =
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
+        private @NonNull TelephonyDisplayInfo mDisplayInfo;
+        private @NonNull SignalStrength mSignalStrength;
+
+        private int mScore;
+
+        private PhoneSignalStatus(@NonNull Phone phone) {
+            this.mPhone = phone;
+            this.mDisplayInfo = phone.getDisplayInfoController().getTelephonyDisplayInfo();
+            this.mSignalStrength = phone.getSignalStrength();
+        }
+        private int updateScore() {
+            // TODO: score = inservice? dcm.getscore() : 0
+            return mScore;
+        }
+        @Override
+        public String toString() {
+            return "{phoneId=" + mPhone.getPhoneId()
+                    + " score=" + mScore + " dataRegState="
+                    + NetworkRegistrationInfo.registrationStateToString(mDataRegState)
+                    + " display=" + mDisplayInfo + " signalStrength=" + mSignalStrength.getLevel()
+                    + "}";
+
+        }
+    }
+
+    /**
+     * This is the callback used for listening events from {@link AutoDataSwitchController}.
+     */
+    public abstract static class AutoDataSwitchControllerCallback {
+        /**
+         * Called when a target data phone is recommended by the controller.
+         * @param targetPhoneId The target phone Id.
+         * @param needValidation {@code true} if need a ping test to pass before switching.
+         */
+        public abstract void onRequireValidation(int targetPhoneId, boolean needValidation);
+
+        /**
+         * Called when a target data phone is demanded by the controller.
+         * @param targetPhoneId The target phone Id.
+         * @param reason The reason for the demand.
+         */
+        public abstract void onRequireImmediatelySwitchToPhone(int targetPhoneId,
+                @AutoDataSwitchEvaluationReason int reason);
+
+        /**
+         * Called when the controller asks to cancel any pending validation attempts because the
+         * environment is no longer suited for switching.
+         */
+        public abstract void onRequireCancelAnyPendingAutoSwitchValidation();
+    }
+
+    /**
+     * @param context Context.
+     * @param looper Main looper.
+     * @param phoneSwitcher Phone switcher.
+     * @param phoneSwitcherCallback Callback for phone switcher to execute.
+     */
+    public AutoDataSwitchController(@NonNull Context context, @NonNull Looper looper,
+            @NonNull PhoneSwitcher phoneSwitcher,
+            @NonNull AutoDataSwitchControllerCallback phoneSwitcherCallback) {
+        super(looper);
+        mContext = context;
+        mSubscriptionManagerService = SubscriptionManagerService.getInstance();
+        mPhoneSwitcher = phoneSwitcher;
+        mPhoneSwitcherCallback = phoneSwitcherCallback;
+        readDeviceResourceConfig();
+        int numActiveModems = PhoneFactory.getPhones().length;
+        mPhonesSignalStatus = new PhoneSignalStatus[numActiveModems];
+        for (int phoneId = 0; phoneId < numActiveModems; phoneId++) {
+            registerAllEventsForPhone(phoneId);
+        }
+    }
+
+    /**
+     * Called when active modem count changed, update all tracking events.
+     * @param numActiveModems The current number of active modems.
+     */
+    public synchronized void onMultiSimConfigChanged(int numActiveModems) {
+        int oldActiveModems = mPhonesSignalStatus.length;
+        if (oldActiveModems == numActiveModems) return;
+        // Dual -> Single
+        for (int phoneId = numActiveModems; phoneId < oldActiveModems; phoneId++) {
+            Phone phone = mPhonesSignalStatus[phoneId].mPhone;
+            phone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(this);
+            phone.getSignalStrengthController().unregisterForSignalStrengthChanged(this);
+            phone.getServiceStateTracker().unregisterForServiceStateChanged(this);
+        }
+        mPhonesSignalStatus = Arrays.copyOf(mPhonesSignalStatus, numActiveModems);
+        // Signal -> Dual
+        for (int phoneId = oldActiveModems; phoneId < numActiveModems; phoneId++) {
+            registerAllEventsForPhone(phoneId);
+        }
+    }
+
+    /**
+     * Register all tracking events for a phone.
+     * @param phoneId The phone to register for all events.
+     */
+    private void registerAllEventsForPhone(int phoneId) {
+        Phone phone = PhoneFactory.getPhone(phoneId);
+        if (phone != null) {
+            mPhonesSignalStatus[phoneId] = new PhoneSignalStatus(phone);
+            phone.getDisplayInfoController().registerForTelephonyDisplayInfoChanged(
+                    this, EVENT_DISPLAY_INFO_CHANGED, phoneId);
+            phone.getSignalStrengthController().registerForSignalStrengthChanged(
+                    this, EVENT_SIGNAL_STRENGTH_CHANGED, phoneId);
+            phone.getServiceStateTracker().registerForServiceStateChanged(this,
+                    EVENT_SERVICE_STATE_CHANGED, phoneId);
+        } else {
+            loge("Unexpected null phone " + phoneId + " when register all events");
+        }
+    }
+
+    /**
+     * Read the default device config from any default phone because the resource config are per
+     * device. No need to register callback for the same reason.
+     */
+    private void readDeviceResourceConfig() {
+        Phone phone = PhoneFactory.getDefaultPhone();
+        DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager();
+        mRequirePingTestBeforeSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired();
+        mAutoDataSwitchAvailabilityStabilityTimeThreshold =
+                dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold();
+        mAutoDataSwitchValidationMaxRetry =
+                dataConfig.getAutoDataSwitchValidationMaxRetry();
+    }
+
+    @Override
+    public void handleMessage(@NonNull Message msg) {
+        AsyncResult ar;
+        int phoneId;
+        switch (msg.what) {
+            case EVENT_SERVICE_STATE_CHANGED:
+                ar = (AsyncResult) msg.obj;
+                phoneId = (int) ar.userObj;
+                onRegistrationStateChanged(phoneId);
+                break;
+            case EVENT_DISPLAY_INFO_CHANGED:
+                ar = (AsyncResult) msg.obj;
+                phoneId = (int) ar.userObj;
+                onDisplayInfoChanged(phoneId);
+                break;
+            case EVENT_EVALUATE_AUTO_SWITCH:
+                int reason = (int) msg.obj;
+                onEvaluateAutoDataSwitch(reason);
+                break;
+            case EVENT_MEETS_AUTO_DATA_SWITCH_STATE:
+                int targetPhoneId = msg.arg1;
+                boolean needValidation = (boolean) msg.obj;
+                log("require validation on phone " + targetPhoneId
+                        + (needValidation ? "" : " no") + " need to pass");
+                mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation);
+                break;
+            default:
+                loge("Unexpected event " + msg.what);
+        }
+    }
+
+    /**
+     * Called when registration state changed.
+     */
+    private void onRegistrationStateChanged(int phoneId) {
+        Phone phone = PhoneFactory.getPhone(phoneId);
+        if (phone != null) {
+            int oldRegState = mPhonesSignalStatus[phoneId].mDataRegState;
+            int newRegState = phone.getServiceState()
+                    .getNetworkRegistrationInfo(
+                            NetworkRegistrationInfo.DOMAIN_PS,
+                            AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                    .getRegistrationState();
+            if (newRegState != oldRegState) {
+                mPhonesSignalStatus[phoneId].mDataRegState = newRegState;
+                log("onRegistrationStateChanged: phone " + phoneId + " "
+                        + NetworkRegistrationInfo.registrationStateToString(oldRegState)
+                        + " -> "
+                        + NetworkRegistrationInfo.registrationStateToString(newRegState));
+                evaluateAutoDataSwitch(EVALUATION_REASON_REGISTRATION_STATE_CHANGED);
+            } else {
+                log("onRegistrationStateChanged: no change.");
+            }
+        } else {
+            loge("Unexpected null phone " + phoneId + " upon its registration state changed");
+        }
+    }
+
+    /**
+     * @return {@code true} if the phone state is considered in service.
+     */
+    private boolean isInService(@NetworkRegistrationInfo.RegistrationState int dataRegState) {
+        return dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME
+                || dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
+    }
+
+    /**
+     * Called when {@link TelephonyDisplayInfo} changed. This can happen when network types or
+     * override network types (5G NSA, 5G MMWAVE) change.
+     */
+    private void onDisplayInfoChanged(int phoneId) {
+        Phone phone = PhoneFactory.getPhone(phoneId);
+        if (phone != null) {
+            TelephonyDisplayInfo displayInfo = phone.getDisplayInfoController()
+                    .getTelephonyDisplayInfo();
+            //TODO(b/260928808)
+            log("onDisplayInfoChanged:" + displayInfo);
+        } else {
+            loge("Unexpected null phone " + phoneId + " upon its display info changed");
+        }
+    }
+
+    /**
+     * Schedule for auto data switch evaluation.
+     * @param reason The reason for the evaluation.
+     */
+    public void evaluateAutoDataSwitch(@AutoDataSwitchEvaluationReason int reason) {
+        long delayMs = reason == EVALUATION_REASON_RETRY_VALIDATION
+                ? mAutoDataSwitchAvailabilityStabilityTimeThreshold
+                << mAutoSwitchValidationFailedCount
+                : 0;
+        if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) {
+            sendMessageDelayed(obtainMessage(EVENT_EVALUATE_AUTO_SWITCH, reason), delayMs);
+        }
+    }
+
+    /**
+     * Evaluate for auto data switch opportunity.
+     * If suitable to switch, check that the suitable state is stable(or switch immediately if user
+     * turned off settings).
+     * @param reason The reason for the evaluation.
+     */
+    private void onEvaluateAutoDataSwitch(@AutoDataSwitchEvaluationReason int reason) {
+        // auto data switch feature is disabled.
+        if (mAutoDataSwitchAvailabilityStabilityTimeThreshold < 0) return;
+        int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId();
+        // check is valid DSDS
+        if (!isActiveSubId(defaultDataSubId) || mSubscriptionManagerService
+                .getActiveSubIdList(true).length <= 1) {
+            return;
+        }
+        Phone defaultDataPhone = PhoneFactory.getPhone(mSubscriptionManagerService.getPhoneId(
+                defaultDataSubId));
+        if (defaultDataPhone == null) {
+            loge("onEvaluateAutoDataSwitch: cannot find the phone associated with default data"
+                    + " subscription " + defaultDataSubId);
+            return;
+        }
+        int defaultDataPhoneId = defaultDataPhone.getPhoneId();
+        int preferredPhoneId = mPhoneSwitcher.getPreferredDataPhoneId();
+        log("onEvaluateAutoDataSwitch: defaultPhoneId: " + defaultDataPhoneId
+                + " preferredPhoneId: " + preferredPhoneId
+                + " reason: " + evaluationReasonToString(reason));
+        if (preferredPhoneId == defaultDataPhoneId) {
+            // on default data sub
+            int candidatePhoneId = getSwitchCandidatePhoneId(defaultDataPhoneId);
+            if (candidatePhoneId != INVALID_PHONE_INDEX) {
+                startStabilityCheck(candidatePhoneId, mRequirePingTestBeforeSwitch);
+            } else {
+                cancelAnyPendingSwitch();
+            }
+        } else {
+            // on backup data sub
+            Phone backupDataPhone = PhoneFactory.getPhone(preferredPhoneId);
+            if (backupDataPhone == null) {
+                loge("onEvaluateAutoDataSwitch: Unexpected null phone " + preferredPhoneId
+                        + " as the current active data phone");
+                return;
+            }
+
+            if (!defaultDataPhone.isUserDataEnabled() || !backupDataPhone.isDataAllowed()) {
+                // immediately switch back if user disabled setting changes
+                mPhoneSwitcherCallback.onRequireImmediatelySwitchToPhone(DEFAULT_PHONE_INDEX,
+                        EVALUATION_REASON_DATA_SETTINGS_CHANGED);
+                return;
+            }
+
+            if (mDefaultNetworkIsOnNonCellular) {
+                log("onEvaluateAutoDataSwitch: Default network is active on nonCellular transport");
+                startStabilityCheck(DEFAULT_PHONE_INDEX, false);
+                return;
+            }
+
+            if (mPhonesSignalStatus[preferredPhoneId].mDataRegState
+                    != NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
+                // backup phone lost its HOME registration
+                startStabilityCheck(DEFAULT_PHONE_INDEX, false);
+                return;
+            }
+
+            if (isInService(mPhonesSignalStatus[defaultDataPhoneId].mDataRegState)) {
+                // default phone is back to service
+                startStabilityCheck(DEFAULT_PHONE_INDEX, mRequirePingTestBeforeSwitch);
+                return;
+            }
+
+            // cancel any previous attempts of switching back to default phone
+            cancelAnyPendingSwitch();
+        }
+    }
+
+    /**
+     * Called when consider switching from primary default data sub to another data sub.
+     * @return the target subId if a suitable candidate is found, otherwise return
+     * {@link SubscriptionManager#INVALID_PHONE_INDEX}
+     */
+    private int getSwitchCandidatePhoneId(int defaultPhoneId) {
+        Phone defaultDataPhone = PhoneFactory.getPhone(defaultPhoneId);
+        if (defaultDataPhone == null) {
+            log("getSwitchCandidatePhoneId: no sim loaded");
+            return INVALID_PHONE_INDEX;
+        }
+
+        if (!defaultDataPhone.isUserDataEnabled()) {
+            log("getSwitchCandidatePhoneId: user disabled data");
+            return INVALID_PHONE_INDEX;
+        }
+
+        if (mDefaultNetworkIsOnNonCellular) {
+            // Exists other active default transport
+            log("getSwitchCandidatePhoneId: Default network is active on non-cellular transport");
+            return INVALID_PHONE_INDEX;
+        }
+
+        // check whether primary and secondary signal status are worth switching
+        if (isInService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) {
+            log("getSwitchCandidatePhoneId: DDS is in service");
+            return INVALID_PHONE_INDEX;
+        }
+        for (int phoneId = 0; phoneId < mPhonesSignalStatus.length; phoneId++) {
+            if (phoneId != defaultPhoneId) {
+                // the alternative phone must have HOME availability
+                if (mPhonesSignalStatus[phoneId].mDataRegState
+                        == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
+                    log("getSwitchCandidatePhoneId: found phone " + phoneId
+                            + " in HOME service");
+                    Phone secondaryDataPhone = PhoneFactory.getPhone(phoneId);
+                    if (secondaryDataPhone != null && // check auto switch feature enabled
+                            secondaryDataPhone.isDataAllowed()) {
+                        return phoneId;
+                    }
+                }
+            }
+        }
+        return INVALID_PHONE_INDEX;
+    }
+
+    /**
+     * Called when the current environment suits auto data switch.
+     * Start pre-switch validation if the current environment suits auto data switch for
+     * {@link #mAutoDataSwitchAvailabilityStabilityTimeThreshold} MS.
+     * @param targetPhoneId the target phone Id.
+     * @param needValidation {@code true} if validation is needed.
+     */
+    private void startStabilityCheck(int targetPhoneId, boolean needValidation) {
+        log("startAutoDataSwitchStabilityCheck: targetPhoneId=" + targetPhoneId
+                + " needValidation=" + needValidation);
+        if (!hasMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, needValidation)) {
+            sendMessageDelayed(obtainMessage(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, targetPhoneId,
+                            0/*placeholder*/,
+                            needValidation),
+                    mAutoDataSwitchAvailabilityStabilityTimeThreshold);
+        }
+    }
+
+    /** Auto data switch evaluation reason to string. */
+    public static @NonNull String evaluationReasonToString(
+            @AutoDataSwitchEvaluationReason int reason) {
+        switch (reason) {
+            case EVALUATION_REASON_REGISTRATION_STATE_CHANGED: return "REGISTRATION_STATE_CHANGED";
+            case EVALUATION_REASON_DISPLAY_INFO_CHANGED: return "DISPLAY_INFO_CHANGED";
+            case EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED: return "SIGNAL_STRENGTH_CHANGED";
+            case EVALUATION_REASON_DEFAULT_NETWORK_CHANGED: return "DEFAULT_NETWORK_CHANGED";
+            case EVALUATION_REASON_DATA_SETTINGS_CHANGED: return "DATA_SETTINGS_CHANGED";
+            case EVALUATION_REASON_RETRY_VALIDATION: return "RETRY_VALIDATION";
+            case EVALUATION_REASON_SIM_LOADED: return "SIM_LOADED";
+            case EVALUATION_REASON_VOICE_CALL_END: return "VOICE_CALL_END";
+        }
+        return "Unknown(" + reason + ")";
+    }
+
+    /** @return {@code true} if the sub is active. */
+    private boolean isActiveSubId(int subId) {
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerService
+                .getSubscriptionInfoInternal(subId);
+        return subInfo != null && subInfo.isActive();
+    }
+
+    /**
+     * Called when default network capabilities changed. If default network is active on
+     * non-cellular, switch back to the default data phone. If default network is lost, try to find
+     * another sub to switch to.
+     * @param networkCapabilities {@code null} indicates default network lost.
+     */
+    public void updateDefaultNetworkCapabilities(
+            @Nullable NetworkCapabilities networkCapabilities) {
+        if (networkCapabilities != null) {
+            // Exists default network
+            mDefaultNetworkIsOnNonCellular = !networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
+            if (mDefaultNetworkIsOnNonCellular
+                    && isActiveSubId(mPhoneSwitcher.getAutoSelectedDataSubId())) {
+                log("default network is active on non cellular, switch back to default");
+                evaluateAutoDataSwitch(EVALUATION_REASON_DEFAULT_NETWORK_CHANGED);
+            }
+        } else {
+            log("default network is lost, try to find another active sub to switch to");
+            mDefaultNetworkIsOnNonCellular = false;
+            evaluateAutoDataSwitch(EVALUATION_REASON_DEFAULT_NETWORK_CHANGED);
+        }
+    }
+
+    /**
+     * Cancel any auto switch attempts when the current environment is not suitable for auto switch.
+     */
+    private void cancelAnyPendingSwitch() {
+        resetFailedCount();
+        removeMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE);
+        mPhoneSwitcherCallback.onRequireCancelAnyPendingAutoSwitchValidation();
+    }
+
+    /**
+     * Display a notification the first time auto data switch occurs.
+     * @param phoneId The phone Id of the current preferred phone.
+     * @param isDueToAutoSwitch {@code true} if the switch was due to auto data switch feature.
+     */
+    public void displayAutoDataSwitchNotification(int phoneId, boolean isDueToAutoSwitch) {
+        NotificationManager notificationManager = (NotificationManager)
+                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        if (mDisplayedNotification) {
+            // cancel posted notification if any exist
+            log("displayAutoDataSwitchNotification: canceling any notifications for phone "
+                    + phoneId);
+            notificationManager.cancel(AUTO_DATA_SWITCH_NOTIFICATION_TAG,
+                    AUTO_DATA_SWITCH_NOTIFICATION_ID);
+            return;
+        }
+        // proceed only the first time auto data switch occurs, which includes data during call
+        if (!isDueToAutoSwitch) {
+            return;
+        }
+        SubscriptionInfo subInfo = mSubscriptionManagerService
+                .getSubscriptionInfo(mSubscriptionManagerService.getSubId(phoneId));
+        if (subInfo == null || subInfo.isOpportunistic()) {
+            loge("displayAutoDataSwitchNotification: phoneId="
+                    + phoneId + " unexpected subInfo " + subInfo);
+            return;
+        }
+        int subId = subInfo.getSubscriptionId();
+        logl("displayAutoDataSwitchNotification: display for subId=" + subId);
+        // "Mobile network settings" screen / dialog
+        Intent intent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS);
+        final Bundle fragmentArgs = new Bundle();
+        // Special contract for Settings to highlight permission row
+        fragmentArgs.putString(SETTINGS_EXTRA_FRAGMENT_ARG_KEY, AUTO_DATA_SWITCH_SETTING_R_ID);
+        intent.putExtra(Settings.EXTRA_SUB_ID, subId);
+        intent.putExtra(SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
+        PendingIntent contentIntent = PendingIntent.getActivity(
+                mContext, subId, intent, PendingIntent.FLAG_IMMUTABLE);
+
+        CharSequence activeCarrierName = subInfo.getDisplayName();
+        CharSequence contentTitle = mContext.getString(
+                com.android.internal.R.string.auto_data_switch_title, activeCarrierName);
+        CharSequence contentText = mContext.getText(
+                com.android.internal.R.string.auto_data_switch_content);
+
+        final Notification notif = new Notification.Builder(mContext)
+                .setContentTitle(contentTitle)
+                .setContentText(contentText)
+                .setSmallIcon(android.R.drawable.stat_sys_warning)
+                .setColor(mContext.getResources().getColor(
+                        com.android.internal.R.color.system_notification_accent_color))
+                .setChannelId(NotificationChannelController.CHANNEL_ID_MOBILE_DATA_STATUS)
+                .setContentIntent(contentIntent)
+                .setStyle(new Notification.BigTextStyle().bigText(contentText))
+                .build();
+        notificationManager.notify(AUTO_DATA_SWITCH_NOTIFICATION_TAG,
+                AUTO_DATA_SWITCH_NOTIFICATION_ID, notif);
+        mDisplayedNotification = true;
+    }
+
+    /** Enable future switch retry again. Called when switch condition changed. */
+    public void resetFailedCount() {
+        mAutoSwitchValidationFailedCount = 0;
+    }
+
+    /**
+     * Called when skipped switch due to validation failed. Schedule retry to switch again.
+     */
+    public void evaluateRetryOnValidationFailed() {
+        if (mAutoSwitchValidationFailedCount < mAutoDataSwitchValidationMaxRetry) {
+            evaluateAutoDataSwitch(EVALUATION_REASON_RETRY_VALIDATION);
+            mAutoSwitchValidationFailedCount++;
+        } else {
+            logl("evaluateRetryOnValidationFailed: reached max auto switch retry count "
+                    + mAutoDataSwitchValidationMaxRetry);
+            mAutoSwitchValidationFailedCount = 0;
+        }
+    }
+
+    /**
+     * Log debug messages.
+     * @param s debug messages
+     */
+    private void log(@NonNull String s) {
+        Rlog.d(LOG_TAG, s);
+    }
+
+    /**
+     * Log error messages.
+     * @param s error messages
+     */
+    private void loge(@NonNull String s) {
+        Rlog.e(LOG_TAG, s);
+    }
+
+    /**
+     * Log debug messages and also log into the local log.
+     * @param s debug messages
+     */
+    private void logl(@NonNull String s) {
+        log(s);
+        mLocalLog.log(s);
+    }
+
+    /**
+     * Dump the state of DataNetworkController
+     *
+     * @param fd File descriptor
+     * @param printWriter Print writer
+     * @param args Arguments
+     */
+    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
+        pw.println("AutoDataSwitchController:");
+        pw.increaseIndent();
+        pw.println("mAutoDataSwitchValidationMaxRetry=" + mAutoDataSwitchValidationMaxRetry
+                + " mAutoSwitchValidationFailedCount=" + mAutoSwitchValidationFailedCount);
+        pw.println("mRequirePingTestBeforeDataSwitch=" + mRequirePingTestBeforeSwitch);
+        pw.println("mAutoDataSwitchAvailabilityStabilityTimeThreshold="
+                + mAutoDataSwitchAvailabilityStabilityTimeThreshold);
+        pw.increaseIndent();
+        for (PhoneSignalStatus status: mPhonesSignalStatus) {
+            pw.println(status);
+        }
+        pw.decreaseIndent();
+        mLocalLog.dump(fd, pw, args);
+        pw.decreaseIndent();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/data/DataConfigManager.java b/src/java/com/android/internal/telephony/data/DataConfigManager.java
index 78450a8..f7fe4ad 100644
--- a/src/java/com/android/internal/telephony/data/DataConfigManager.java
+++ b/src/java/com/android/internal/telephony/data/DataConfigManager.java
@@ -32,6 +32,7 @@
 import android.telephony.Annotation.NetworkType;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
@@ -199,8 +200,6 @@
             "anomaly_setup_data_call_failure";
     /** DeviceConfig key of anomaly report threshold for frequent network-unwanted call. */
     private static final String KEY_ANOMALY_NETWORK_UNWANTED = "anomaly_network_unwanted";
-    /** DeviceConfig key of anomaly report threshold for frequent change of preferred network. */
-    private static final String KEY_ANOMALY_QNS_CHANGE_NETWORK = "anomaly_qns_change_network";
     /** DeviceConfig key of anomaly report threshold for invalid QNS params. */
     private static final String KEY_ANOMALY_QNS_PARAM = "anomaly_qns_param";
     /** DeviceConfig key of anomaly report threshold for DataNetwork stuck in connecting state. */
@@ -214,13 +213,8 @@
             "anomaly_network_handover_timeout";
     /** DeviceConfig key of anomaly report: True for enabling APN config invalidity detection */
     private static final String KEY_ANOMALY_APN_CONFIG_ENABLED = "anomaly_apn_config_enabled";
-    /** DeviceConfig key of the time threshold in ms for defining a network status to be stable. **/
-    private static final String KEY_AUTO_DATA_SWITCH_AVAILABILITY_STABILITY_TIME_THRESHOLD =
-            "auto_data_switch_availability_stability_time_threshold";
-    /** DeviceConfig key of the maximum number of retries when a validation for switching failed.**/
-    private static final String KEY_AUTO_DATA_SWITCH_VALIDATION_MAX_RETRY =
-            "auto_data_switch_validation_max_retry";
-
+    /** Invalid auto data switch score. */
+    private static final int INVALID_AUTO_DATA_SWITCH_SCORE = -1;
     /** Anomaly report thresholds for frequent setup data call failure. */
     private EventFrequency mSetupDataCallAnomalyReportThreshold;
 
@@ -301,6 +295,12 @@
     private @NonNull final List<HandoverRule> mHandoverRuleList = new ArrayList<>();
     /** {@code True} keep IMS network in case of moving to non VOPS area; {@code false} otherwise.*/
     private boolean mShouldKeepNetworkUpInNonVops = false;
+    /**
+     * A map of network types to the estimated downlink values by signal strength 0 - 4 for that
+     * network type
+     */
+    private @NonNull final @DataConfigNetworkType Map<String, int[]>
+            mAutoDataSwitchNetworkTypeSignalMap = new ConcurrentHashMap<>();
 
     /**
      * Constructor
@@ -452,6 +452,7 @@
         updateBandwidths();
         updateTcpBuffers();
         updateHandoverRules();
+        updateAutoDataSwitchConfig();
 
         log("Carrier config updated. Config is " + (isConfigCarrierSpecific() ? "" : "not ")
                 + "carrier specific.");
@@ -918,6 +919,84 @@
     }
 
     /**
+     * Update the network type and signal strength score table for auto data switch decisions.
+     */
+    private void updateAutoDataSwitchConfig() {
+        synchronized (this) {
+            mAutoDataSwitchNetworkTypeSignalMap.clear();
+            final PersistableBundle table = mCarrierConfig.getPersistableBundle(
+                    CarrierConfigManager.KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_BUNDLE);
+            String[] networkTypeKeys = {
+                    DATA_CONFIG_NETWORK_TYPE_GPRS,
+                    DATA_CONFIG_NETWORK_TYPE_EDGE,
+                    DATA_CONFIG_NETWORK_TYPE_UMTS,
+                    DATA_CONFIG_NETWORK_TYPE_CDMA,
+                    DATA_CONFIG_NETWORK_TYPE_1xRTT,
+                    DATA_CONFIG_NETWORK_TYPE_EVDO_0,
+                    DATA_CONFIG_NETWORK_TYPE_EVDO_A,
+                    DATA_CONFIG_NETWORK_TYPE_HSDPA,
+                    DATA_CONFIG_NETWORK_TYPE_HSUPA,
+                    DATA_CONFIG_NETWORK_TYPE_HSPA,
+                    DATA_CONFIG_NETWORK_TYPE_EVDO_B,
+                    DATA_CONFIG_NETWORK_TYPE_EHRPD,
+                    DATA_CONFIG_NETWORK_TYPE_IDEN,
+                    DATA_CONFIG_NETWORK_TYPE_LTE,
+                    DATA_CONFIG_NETWORK_TYPE_HSPAP,
+                    DATA_CONFIG_NETWORK_TYPE_GSM,
+                    DATA_CONFIG_NETWORK_TYPE_TD_SCDMA,
+                    DATA_CONFIG_NETWORK_TYPE_NR_NSA,
+                    DATA_CONFIG_NETWORK_TYPE_NR_NSA_MMWAVE,
+                    DATA_CONFIG_NETWORK_TYPE_NR_SA,
+                    DATA_CONFIG_NETWORK_TYPE_NR_SA_MMWAVE
+            };
+            if (table != null) {
+                for (String networkType : networkTypeKeys) {
+                    int[] scores = table.getIntArray(networkType);
+                    if (scores != null
+                            && scores.length == SignalStrength.NUM_SIGNAL_STRENGTH_BINS) {
+                        for (int i = 0; i < scores.length; i++) {
+                            if (scores[i] < 0) {
+                                loge("Auto switch score must not < 0 for network type "
+                                        + networkType);
+                                break;
+                            }
+                            if (i == scores.length - 1) {
+                                mAutoDataSwitchNetworkTypeSignalMap.put(networkType, scores);
+                            }
+                        }
+                    } else {
+                        loge("Auto switch score table should specify "
+                                + SignalStrength.NUM_SIGNAL_STRENGTH_BINS
+                                + " signal strength for network type " + networkType);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * @param displayInfo The displayed network info.
+     * @param signalStrength The signal strength.
+     * @return Score base on network type and signal strength to inform auto data switch decision.
+     */
+    public int getAutoDataSwitchScore(@NonNull TelephonyDisplayInfo displayInfo,
+            @NonNull SignalStrength signalStrength) {
+        int[] scores = mAutoDataSwitchNetworkTypeSignalMap.get(
+                getDataConfigNetworkType(displayInfo));
+        return scores != null ? scores[signalStrength.getLevel()] : INVALID_AUTO_DATA_SWITCH_SCORE;
+    }
+
+    /**
+     * @return The tolerated gap of score for auto data switch decision, larger than which the
+     * device will switch to the SIM with higher score. If 0, the device always switch to the higher
+     * score SIM. If < 0, the network type and signal strength based auto switch is disabled.
+     */
+    public int getAutoDataSwitchScoreTolerance() {
+        return mResources.getInteger(com.android.internal.R.integer
+                .auto_data_switch_score_tolerance);
+    }
+
+    /**
      * @return The maximum number of retries when a validation for switching failed.
      */
     public int getAutoDataSwitchValidationMaxRetry() {
@@ -1264,6 +1343,15 @@
     }
 
     /**
+     * @return {@code true} if allow sending null data profile to ask modem to clear the initial
+     * attach data profile.
+     */
+    public boolean allowClearInitialAttachDataProfile() {
+        return mResources.getBoolean(
+                com.android.internal.R.bool.allow_clear_initial_attach_data_profile);
+    }
+
+    /**
      * Log debug messages.
      * @param s debug messages
      */
@@ -1315,9 +1403,15 @@
         pw.println("mNetworkDisconnectingTimeout=" + mNetworkDisconnectingTimeout);
         pw.println("mNetworkHandoverTimeout=" + mNetworkHandoverTimeout);
         pw.println("mIsApnConfigAnomalyReportEnabled=" + mIsApnConfigAnomalyReportEnabled);
+        pw.println("Auto data switch:");
+        pw.increaseIndent();
+        pw.println("getAutoDataSwitchScoreTolerance=" + getAutoDataSwitchScoreTolerance());
+        mAutoDataSwitchNetworkTypeSignalMap.forEach((key, value) -> pw.println(key + ":"
+                + Arrays.toString(value)));
         pw.println("getAutoDataSwitchAvailabilityStabilityTimeThreshold="
                 + getAutoDataSwitchAvailabilityStabilityTimeThreshold());
         pw.println("getAutoDataSwitchValidationMaxRetry=" + getAutoDataSwitchValidationMaxRetry());
+        pw.decreaseIndent();
         pw.println("Metered APN types=" + mMeteredApnTypes.stream()
                 .map(ApnSetting::getApnTypeString).collect(Collectors.joining(",")));
         pw.println("Roaming metered APN types=" + mRoamingMeteredApnTypes.stream()
@@ -1359,6 +1453,7 @@
         pw.println("isEnhancedIwlanHandoverCheckEnabled=" + isEnhancedIwlanHandoverCheckEnabled());
         pw.println("isTetheringProfileDisabledForRoaming="
                 + isTetheringProfileDisabledForRoaming());
+        pw.println("allowClearInitialAttachDataProfile=" + allowClearInitialAttachDataProfile());
         pw.decreaseIndent();
     }
 }
diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java
index d533933..6ba251b 100644
--- a/src/java/com/android/internal/telephony/data/DataNetwork.java
+++ b/src/java/com/android/internal/telephony/data/DataNetwork.java
@@ -664,6 +664,11 @@
      */
     private @NetworkType int mLastKnownDataNetworkType;
 
+    /**
+     * The last known roaming state of this data network.
+     */
+    private boolean mLastKnownRoamingState;
+
     /** The reason that why setting up this data network is allowed. */
     private @NonNull DataAllowedReason mDataAllowedReason;
 
@@ -923,6 +928,7 @@
         }
         mTransport = transport;
         mLastKnownDataNetworkType = getDataNetworkType();
+        mLastKnownRoamingState = mPhone.getServiceState().getDataRoamingFromRegistration();
         mDataAllowedReason = dataAllowedReason;
         dataProfile.setLastSetupTimestamp(SystemClock.elapsedRealtime());
         mAttachedNetworkRequestList.addAll(networkRequestList);
@@ -1136,6 +1142,15 @@
                     if (networkType != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
                         mLastKnownDataNetworkType = networkType;
                     }
+                    NetworkRegistrationInfo nri =
+                            mPhone.getServiceState()
+                                    .getNetworkRegistrationInfo(
+                                            NetworkRegistrationInfo.DOMAIN_PS,
+                                            AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+                    if (nri != null && nri.isInService()) {
+                        mLastKnownRoamingState = nri.getNetworkRegistrationState()
+                                == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
+                    }
                     updateSuspendState();
                     updateNetworkCapabilities();
                     break;
@@ -2224,7 +2239,11 @@
 
         TrafficDescriptor trafficDescriptor = mDataProfile.getTrafficDescriptor();
         final boolean matchAllRuleAllowed = trafficDescriptor == null
-                || !TextUtils.isEmpty(trafficDescriptor.getDataNetworkName());
+                || !TextUtils.isEmpty(trafficDescriptor.getDataNetworkName())
+                // Both OsAppId and APN name are null. This helps for modem to handle when we
+                // are on 5G or LTE with URSP support in falling back to default network.
+                || (TextUtils.isEmpty(trafficDescriptor.getDataNetworkName())
+                && trafficDescriptor.getOsAppId() == null);
 
         int accessNetwork = DataUtils.networkTypeToAccessNetworkType(dataNetworkType);
 
@@ -3391,6 +3410,13 @@
     }
 
     /**
+     * @return The last known roaming state of this data network.
+     */
+    public boolean getLastKnownRoamingState() {
+        return mLastKnownRoamingState;
+    }
+
+    /**
      * @return The PCO data received from the network.
      */
     public @NonNull Map<Integer, PcoData> getPcoData() {
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index 2ee1ffd..f9e7510 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -1983,21 +1983,31 @@
             }
             int sourceAccessNetwork = DataUtils.networkTypeToAccessNetworkType(
                     sourceNetworkType);
-
+            NetworkRegistrationInfo nri = mServiceState.getNetworkRegistrationInfo(
+                    NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+            boolean isWwanInService = false;
+            if (nri != null && nri.isInService()) {
+                isWwanInService = true;
+            }
+            // If WWAN is inService, use the real roaming state reported by modem instead of
+            // using the overridden roaming state, otherwise get last known roaming state stored
+            // in data network.
+            boolean isRoaming = isWwanInService ? mServiceState.getDataRoamingFromRegistration()
+                    : dataNetwork.getLastKnownRoamingState();
             int targetAccessNetwork = DataUtils.networkTypeToAccessNetworkType(
                     getDataNetworkType(DataUtils.getTargetTransport(dataNetwork.getTransport())));
             NetworkCapabilities capabilities = dataNetwork.getNetworkCapabilities();
             log("evaluateDataNetworkHandover: "
                     + "source=" + AccessNetworkType.toString(sourceAccessNetwork)
                     + ", target=" + AccessNetworkType.toString(targetAccessNetwork)
+                    + ", roaming=" + isRoaming
                     + ", ServiceState=" + mServiceState
                     + ", capabilities=" + capabilities);
 
             // Matching the rules by the configured order. Bail out if find first matching rule.
             for (HandoverRule rule : handoverRules) {
-                // Check if the rule is only for roaming and we are not roaming. Use the real
-                // roaming state reported by modem instead of using the overridden roaming state.
-                if (rule.isOnlyForRoaming && !mServiceState.getDataRoamingFromRegistration()) {
+                // Check if the rule is only for roaming and we are not roaming.
+                if (rule.isOnlyForRoaming && !isRoaming) {
                     // If the rule is for roaming only, and the device is not roaming, then bypass
                     // this rule.
                     continue;
@@ -3378,7 +3388,8 @@
 
         if (oldPsNri == null
                 || oldPsNri.getAccessNetworkTechnology() != newPsNri.getAccessNetworkTechnology()
-                || (!oldPsNri.isInService() && newPsNri.isInService())) {
+                || (!oldPsNri.isInService() && newPsNri.isInService())
+                || (oldPsNri.isRoaming() && !newPsNri.isRoaming())) {
             return true;
         }
 
diff --git a/src/java/com/android/internal/telephony/data/DataProfileManager.java b/src/java/com/android/internal/telephony/data/DataProfileManager.java
index 893ec41..0878ccf 100644
--- a/src/java/com/android/internal/telephony/data/DataProfileManager.java
+++ b/src/java/com/android/internal/telephony/data/DataProfileManager.java
@@ -78,13 +78,6 @@
     private final String mLogTag;
     private final LocalLog mLocalLog = new LocalLog(128);
 
-    /**
-     * Should only be used by update updateDataProfiles() to indicate whether resend IA to modem
-     * regardless whether IA changed.
-     **/
-    private final boolean FORCED_UPDATE_IA = true;
-    private final boolean ONLY_UPDATE_IA_IF_CHANGED = false;
-
     /** Data network controller. */
     private final @NonNull DataNetworkController mDataNetworkController;
 
@@ -202,11 +195,12 @@
         switch (msg.what) {
             case EVENT_SIM_REFRESH:
                 log("Update data profiles due to SIM refresh.");
-                updateDataProfiles(FORCED_UPDATE_IA);
+                updateDataProfiles(!mDataConfigManager.allowClearInitialAttachDataProfile()
+                        /*force update IA*/);
                 break;
             case EVENT_APN_DATABASE_CHANGED:
                 log("Update data profiles due to APN db updated.");
-                updateDataProfiles(ONLY_UPDATE_IA_IF_CHANGED);
+                updateDataProfiles(false/*force update IA*/);
                 break;
             default:
                 loge("Unexpected event " + msg);
@@ -219,9 +213,8 @@
      */
     private void onCarrierConfigUpdated() {
         log("Update data profiles due to carrier config updated.");
-        updateDataProfiles(FORCED_UPDATE_IA);
-
-        //TODO: more works needed to be done here.
+        updateDataProfiles(!mDataConfigManager.allowClearInitialAttachDataProfile()
+                /*force update IA*/);
     }
 
     /**
@@ -258,7 +251,8 @@
      * Update all data profiles, including preferred data profile, and initial attach data profile.
      * Also send those profiles down to the modem if needed.
      *
-     * @param forceUpdateIa If {@code true}, we should always send IA again to modem.
+     * @param forceUpdateIa If {@code true}, we should always send initial attach data profile again
+     *                     to modem.
      */
     private void updateDataProfiles(boolean forceUpdateIa) {
         List<DataProfile> profiles = new ArrayList<>();
@@ -444,7 +438,7 @@
         if (defaultProfile == null || defaultProfile.equals(mPreferredDataProfile)) return;
         // Save the preferred data profile into database.
         setPreferredDataProfile(defaultProfile);
-        updateDataProfiles(ONLY_UPDATE_IA_IF_CHANGED);
+        updateDataProfiles(false/*force update IA*/);
     }
 
     /**
@@ -567,7 +561,8 @@
      * attach. In this case, exception can be configured through
      * {@link CarrierConfigManager#KEY_ALLOWED_INITIAL_ATTACH_APN_TYPES_STRING_ARRAY}.
      *
-     * @param forceUpdateIa If {@code true}, we should always send IA again to modem.
+     * @param forceUpdateIa If {@code true}, we should always send initial attach data profile again
+     *                     to modem.
      */
     private void updateInitialAttachDataProfileAtModem(boolean forceUpdateIa) {
         DataProfile initialAttachDataProfile = null;
@@ -589,9 +584,8 @@
             mInitialAttachDataProfile = initialAttachDataProfile;
             logl("Initial attach data profile updated as " + mInitialAttachDataProfile
                     + " or forceUpdateIa= " + forceUpdateIa);
-            // TODO: Push the null data profile to modem on new AIDL HAL. Modem should clear the IA
-            //  APN, tracking for U b/227579876, now using forceUpdateIa which always push to modem
-            if (mInitialAttachDataProfile != null) {
+            if (mInitialAttachDataProfile != null || mDataConfigManager
+                    .allowClearInitialAttachDataProfile()) {
                 mWwanDataServiceManager.setInitialAttachApn(mInitialAttachDataProfile,
                         mPhone.getServiceState().getDataRoamingFromRegistration(), null);
             }
diff --git a/src/java/com/android/internal/telephony/data/DataRetryManager.java b/src/java/com/android/internal/telephony/data/DataRetryManager.java
index b6ad101..3146689 100644
--- a/src/java/com/android/internal/telephony/data/DataRetryManager.java
+++ b/src/java/com/android/internal/telephony/data/DataRetryManager.java
@@ -1469,9 +1469,9 @@
         } else {
             Intent intent = new Intent(ACTION_RETRY);
             intent.putExtra(ACTION_RETRY_EXTRA_HASHCODE, dataRetryEntry.hashCode());
-            // No need to wake up the device at the exact time, the retry can wait util next time
-            // the device wake up to save power.
-            mAlarmManager.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME,
+            // No need to wake up the device, the retry can wait util next time the device wake up
+            // to save power.
+            mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME,
                     dataRetryEntry.retryElapsedTime,
                     PendingIntent.getBroadcast(mPhone.getContext(),
                             dataRetryEntry.hashCode() /*Unique identifier of this retry attempt*/,
diff --git a/src/java/com/android/internal/telephony/data/DataServiceManager.java b/src/java/com/android/internal/telephony/data/DataServiceManager.java
index 2733aff..bb0e8c3 100644
--- a/src/java/com/android/internal/telephony/data/DataServiceManager.java
+++ b/src/java/com/android/internal/telephony/data/DataServiceManager.java
@@ -809,11 +809,12 @@
      * Set an APN to initial attach network.
      *
      * @param dataProfile Data profile used for data network setup. See {@link DataProfile}.
+     *                  {@code null} to clear any previous data profiles.
      * @param isRoaming True if the device is data roaming.
      * @param onCompleteMessage The result message for this request. Null if the client does not
      * care about the result.
      */
-    public void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming,
+    public void setInitialAttachApn(@Nullable DataProfile dataProfile, boolean isRoaming,
                                     Message onCompleteMessage) {
         if (DBG) log("setInitialAttachApn");
         if (!mBound) {
diff --git a/src/java/com/android/internal/telephony/data/DataSettingsManager.java b/src/java/com/android/internal/telephony/data/DataSettingsManager.java
index 5178ae4..f8365bb 100644
--- a/src/java/com/android/internal/telephony/data/DataSettingsManager.java
+++ b/src/java/com/android/internal/telephony/data/DataSettingsManager.java
@@ -130,6 +130,14 @@
         }
 
         /**
+         * Called when user data enabled state changed.
+         *
+         * @param enabled {@code true} indicates user mobile data is enabled.
+         * @param callingPackage The package that changed the data enabled state.
+         */
+        public void onUserDataEnabledChanged(boolean enabled, @NonNull String callingPackage) {}
+
+        /**
          * Called when overall data enabled state changed.
          *
          * @param enabled {@code true} indicates mobile data is enabled.
@@ -289,6 +297,23 @@
                         }
                     }
                 }, this::post);
+        // some overall mobile data override policy depend on whether DDS is user data enabled.
+        for (Phone phone : PhoneFactory.getPhones()) {
+            if (phone.getPhoneId() != mPhone.getPhoneId()) {
+                phone.getDataSettingsManager().registerCallback(new DataSettingsManagerCallback(
+                        this::post) {
+                    @Override
+                    public void onUserDataEnabledChanged(boolean enabled,
+                            @NonNull String callingPackage) {
+                        log("phone" + phone.getPhoneId() + " onUserDataEnabledChanged "
+                                + enabled + " by " + callingPackage
+                                + ", reevaluating mobile data policies");
+                        DataSettingsManager.this.updateDataEnabledAndNotify(
+                                TelephonyManager.DATA_ENABLED_REASON_OVERRIDE);
+                    }
+                });
+            }
+        }
         updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_UNKNOWN);
     }
 
@@ -416,6 +441,8 @@
         if (changed) {
             logl("UserDataEnabled changed to " + enabled);
             mPhone.notifyUserMobileDataStateChanged(enabled);
+            mDataSettingsManagerCallbacks.forEach(callback -> callback.invokeFromExecutor(
+                    () -> callback.onUserDataEnabledChanged(enabled, callingPackage)));
             updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_USER, callingPackage);
         }
     }
diff --git a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
index 6c6f064..375b250 100644
--- a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
+++ b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
@@ -23,7 +23,10 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.content.Intent;
+import android.database.ContentObserver;
 import android.net.NetworkAgent;
+import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -34,6 +37,7 @@
 import android.telephony.CellSignalStrength;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.IndentingPrintWriter;
 import android.util.LocalLog;
 
@@ -127,12 +131,24 @@
     /** The data stall recovered by user (Mobile Data Power off/on). */
     private static final int RECOVERED_REASON_USER = 3;
 
+    /** The number of milliseconds to wait for the DSRM prediction to complete. */
+    private static final int DSRM_PREDICT_WAITING_MILLIS = 1000;
+
+    /** Event for send data stall broadcast. */
+    private static final int EVENT_SEND_DATA_STALL_BROADCAST = 1;
+
     /** Event for triggering recovery action. */
     private static final int EVENT_DO_RECOVERY = 2;
 
     /** Event for radio state changed. */
     private static final int EVENT_RADIO_STATE_CHANGED = 3;
 
+    /** Event for recovery actions changed. */
+    private static final int EVENT_CONTENT_DSRM_ENABLED_ACTIONS_CHANGED = 4;
+
+    /** Event for duration milliseconds changed. */
+    private static final int EVENT_CONTENT_DSRM_DURATION_MILLIS_CHANGED = 5;
+
     private final @NonNull Phone mPhone;
     private final @NonNull String mLogTag;
     private final @NonNull LocalLog mLocalLog = new LocalLog(128);
@@ -185,8 +201,30 @@
     /** The boolean array for the flags. They are used to skip the recovery actions if needed. */
     private @NonNull boolean[] mSkipRecoveryActionArray;
 
+    /**
+     * The content URI for the DSRM recovery actions.
+     *
+     * @see Settings.Global#DSRM_ENABLED_ACTIONS
+     */
+    private static final Uri CONTENT_URL_DSRM_ENABLED_ACTIONS = Settings.Global.getUriFor(
+            Settings.Global.DSRM_ENABLED_ACTIONS);
+
+    /**
+     * The content URI for the DSRM duration milliseconds.
+     *
+     * @see Settings.Global#DSRM_DURATION_MILLIS
+     */
+    private static final Uri CONTENT_URL_DSRM_DURATION_MILLIS = Settings.Global.getUriFor(
+            Settings.Global.DSRM_DURATION_MILLIS);
+
+
     private DataStallRecoveryManagerCallback mDataStallRecoveryManagerCallback;
 
+    private final DataStallRecoveryStats mStats;
+
+    /** The number of milliseconds to wait for the DSRM prediction to complete. */
+    private @ElapsedRealtimeLong long mPredictWaitingMillis = 0L;
+
     /**
      * The data stall recovery manager callback. Note this is only used for passing information
      * internally in the data stack, should not be used externally.
@@ -248,6 +286,8 @@
         updateDataStallRecoveryConfigs();
 
         registerAllEvents();
+
+        mStats = new DataStallRecoveryStats(mPhone, dataNetworkController);
     }
 
     /** Register for all events that data stall monitor is interested. */
@@ -280,12 +320,33 @@
                     }
                 });
         mPhone.mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
+
+        // Register for DSRM global setting changes.
+        mPhone.getContext().getContentResolver().registerContentObserver(
+                CONTENT_URL_DSRM_ENABLED_ACTIONS, false, mContentObserver);
+        mPhone.getContext().getContentResolver().registerContentObserver(
+                CONTENT_URL_DSRM_DURATION_MILLIS, false, mContentObserver);
+
     }
 
     @Override
     public void handleMessage(Message msg) {
         logv("handleMessage = " + msg);
         switch (msg.what) {
+            case EVENT_SEND_DATA_STALL_BROADCAST:
+                mRecoveryTriggered = true;
+                // Verify that DSRM needs to process the recovery action
+                if (!isRecoveryNeeded(false)) {
+                    cancelNetworkCheckTimer();
+                    startNetworkCheckTimer(getRecoveryAction());
+                    return;
+                }
+                // Broadcast intent that data stall has been detected.
+                broadcastDataStallDetected(getRecoveryAction());
+                // Schedule the message to to wait for the predicted value.
+                sendMessageDelayed(
+                        obtainMessage(EVENT_DO_RECOVERY), mPredictWaitingMillis);
+                break;
             case EVENT_DO_RECOVERY:
                 doRecovery();
                 break;
@@ -302,16 +363,119 @@
                     }
                 }
                 break;
+            case EVENT_CONTENT_DSRM_ENABLED_ACTIONS_CHANGED:
+                updateGlobalConfigActions();
+                break;
+            case EVENT_CONTENT_DSRM_DURATION_MILLIS_CHANGED:
+                updateGlobalConfigDurations();
+                break;
             default:
                 loge("Unexpected message = " + msg);
                 break;
         }
     }
 
+    private final ContentObserver mContentObserver = new ContentObserver(this) {
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            super.onChange(selfChange, uri);
+            if (CONTENT_URL_DSRM_ENABLED_ACTIONS.equals(uri)) {
+                log("onChange URI: " + uri);
+                sendMessage(obtainMessage(EVENT_CONTENT_DSRM_ENABLED_ACTIONS_CHANGED));
+            } else if (CONTENT_URL_DSRM_DURATION_MILLIS.equals(uri)) {
+                log("onChange URI: " + uri);
+                sendMessage(obtainMessage(EVENT_CONTENT_DSRM_DURATION_MILLIS_CHANGED));
+            }
+        }
+    };
+
+
+    @VisibleForTesting
+    public ContentObserver getContentObserver() {
+        return mContentObserver;
+    }
+
+    /**
+     * Updates the skip recovery action array based on DSRM global configuration actions.
+     *
+     * @see Settings.Global#DSRM_ENABLED_ACTIONS
+     */
+    private void updateGlobalConfigActions() {
+        String enabledActions = Settings.Global.getString(
+                mPhone.getContext().getContentResolver(),
+                Settings.Global.DSRM_ENABLED_ACTIONS);
+
+        if (!TextUtils.isEmpty(enabledActions)) {
+            String[] splitEnabledActions = enabledActions.split(",");
+            boolean[] enabledActionsArray = new boolean[splitEnabledActions.length];
+            for (int i = 0; i < enabledActionsArray.length; i++) {
+                enabledActionsArray[i] = Boolean.parseBoolean(splitEnabledActions[i].trim());
+            }
+
+            int minLength = Math.min(enabledActionsArray.length,
+                    mSkipRecoveryActionArray.length);
+
+            // Update the skip recovery action array.
+            for (int i = 0; i < minLength; i++) {
+                mSkipRecoveryActionArray[i] = !enabledActionsArray[i];
+            }
+            log("SkipRecoveryAction: "
+                    + Arrays.toString(mSkipRecoveryActionArray));
+            mPredictWaitingMillis = DSRM_PREDICT_WAITING_MILLIS;
+        } else {
+            mPredictWaitingMillis = 0;
+            log("Enabled actions is null");
+        }
+    }
+
+    /**
+     * Updates the duration millis array based on DSRM global configuration durations.
+     *
+     * @see Settings.Global#DSRM_DURATION_MILLIS
+     */
+    private void updateGlobalConfigDurations() {
+        String durationMillis = Settings.Global.getString(
+                mPhone.getContext().getContentResolver(),
+                Settings.Global.DSRM_DURATION_MILLIS);
+
+        if (!TextUtils.isEmpty(durationMillis)) {
+            String[] splitDurationMillis = durationMillis.split(",");
+            long[] durationMillisArray = new long[splitDurationMillis.length];
+            for (int i = 0; i < durationMillisArray.length; i++) {
+                try {
+                    durationMillisArray[i] = Long.parseLong(splitDurationMillis[i].trim());
+                } catch (NumberFormatException e) {
+                    mPredictWaitingMillis = 0;
+                    loge("Parsing duration millis error");
+                    return;
+                }
+            }
+
+            int minLength = Math.min(durationMillisArray.length,
+                    mDataStallRecoveryDelayMillisArray.length);
+
+            // Copy the values from the durationMillisArray array to the
+            // mDataStallRecoveryDelayMillisArray array.
+            for (int i = 0; i < minLength; i++) {
+                mDataStallRecoveryDelayMillisArray[i] = durationMillisArray[i];
+            }
+            log("DataStallRecoveryDelayMillis: "
+                    + Arrays.toString(mDataStallRecoveryDelayMillisArray));
+            mPredictWaitingMillis = DSRM_PREDICT_WAITING_MILLIS;
+        } else {
+            mPredictWaitingMillis = 0;
+            log("Duration millis is null");
+        }
+    }
+
     /** Update the data stall recovery configs from DataConfigManager. */
     private void updateDataStallRecoveryConfigs() {
         mDataStallRecoveryDelayMillisArray = mDataConfigManager.getDataStallRecoveryDelayMillis();
         mSkipRecoveryActionArray = mDataConfigManager.getDataStallRecoveryShouldSkipArray();
+
+        //Update Global settings
+        updateGlobalConfigActions();
+        updateGlobalConfigDurations();
     }
 
     /**
@@ -320,7 +484,8 @@
      * @param recoveryAction The recovery action to query.
      * @return the delay in milliseconds for the specific recovery action.
      */
-    private long getDataStallRecoveryDelayMillis(@RecoveryAction int recoveryAction) {
+    @VisibleForTesting
+    public long getDataStallRecoveryDelayMillis(@RecoveryAction int recoveryAction) {
         return mDataStallRecoveryDelayMillisArray[recoveryAction];
     }
 
@@ -330,7 +495,8 @@
      * @param recoveryAction The recovery action.
      * @return {@code true} if the action needs to be skipped.
      */
-    private boolean shouldSkipRecoveryAction(@RecoveryAction int recoveryAction) {
+    @VisibleForTesting
+    public boolean shouldSkipRecoveryAction(@RecoveryAction int recoveryAction) {
         return mSkipRecoveryActionArray[recoveryAction];
     }
 
@@ -385,7 +551,7 @@
             mIsValidNetwork = false;
             log("trigger data stall recovery");
             mTimeLastRecoveryStartMs = SystemClock.elapsedRealtime();
-            sendMessage(obtainMessage(EVENT_DO_RECOVERY));
+            sendMessage(obtainMessage(EVENT_SEND_DATA_STALL_BROADCAST));
         }
     }
 
@@ -490,6 +656,22 @@
         Intent intent = new Intent(TelephonyManager.ACTION_DATA_STALL_DETECTED);
         SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
         intent.putExtra(TelephonyManager.EXTRA_RECOVERY_ACTION, recoveryAction);
+
+        // Get the information for DSRS state
+        final boolean isRecovered = !mDataStalled;
+        final int duration = (int) (SystemClock.elapsedRealtime() - mDataStallStartMs);
+        final @RecoveredReason int reason = getRecoveredReason(mIsValidNetwork);
+        final boolean isFirstValidationOfAction = false;
+        final int durationOfAction = (int) getDurationOfCurrentRecoveryMs();
+
+        // Get the bundled DSRS stats.
+        Bundle bundle = mStats.getDataStallRecoveryMetricsData(
+                recoveryAction, isRecovered, duration, reason, isFirstValidationOfAction,
+                durationOfAction);
+
+        // Put the bundled stats extras on the intent.
+        intent.putExtra("EXTRA_DSRS_STATS_BUNDLE", bundle);
+
         mPhone.getContext().sendBroadcast(intent, READ_PRIVILEGED_PHONE_STATE);
     }
 
@@ -531,7 +713,8 @@
             mNetworkCheckTimerStarted = true;
             mTimeLastRecoveryStartMs = SystemClock.elapsedRealtime();
             sendMessageDelayed(
-                    obtainMessage(EVENT_DO_RECOVERY), getDataStallRecoveryDelayMillis(action));
+                    obtainMessage(EVENT_SEND_DATA_STALL_BROADCAST),
+                    getDataStallRecoveryDelayMillis(action));
         }
     }
 
@@ -540,7 +723,7 @@
         log("cancelNetworkCheckTimer()");
         if (mNetworkCheckTimerStarted) {
             mNetworkCheckTimerStarted = false;
-            removeMessages(EVENT_DO_RECOVERY);
+            removeMessages(EVENT_SEND_DATA_STALL_BROADCAST);
         }
     }
 
@@ -645,8 +828,9 @@
                    && !mIsAttemptedAllSteps)
                  || mLastAction == RECOVERY_ACTION_RESET_MODEM)
                  ? (int) getDurationOfCurrentRecoveryMs() : 0;
-            DataStallRecoveryStats.onDataStallEvent(
-                    mLastAction, mPhone, isValid, timeDuration, reason,
+
+            mStats.uploadMetrics(
+                    mLastAction, isValid, timeDuration, reason,
                     isFirstValidationAfterDoRecovery, timeDurationOfCurrentAction);
             logl(
                     "data stall: "
@@ -696,22 +880,12 @@
     private void doRecovery() {
         @RecoveryAction final int recoveryAction = getRecoveryAction();
         final int signalStrength = mPhone.getSignalStrength().getLevel();
-        mRecoveryTriggered = true;
-
-        // DSRM used sendMessageDelayed to process the next event EVENT_DO_RECOVERY, so it need
-        // to check the condition if DSRM need to process the recovery action.
-        if (!isRecoveryNeeded(false)) {
-            cancelNetworkCheckTimer();
-            startNetworkCheckTimer(recoveryAction);
-            return;
-        }
 
         TelephonyMetrics.getInstance()
                 .writeSignalStrengthEvent(mPhone.getPhoneId(), signalStrength);
         TelephonyMetrics.getInstance().writeDataStallEvent(mPhone.getPhoneId(), recoveryAction);
         mLastAction = recoveryAction;
         mLastActionReported = false;
-        broadcastDataStallDetected(recoveryAction);
         mNetworkCheckTimerStarted = false;
         mTimeElapsedOfCurrentAction = SystemClock.elapsedRealtime();
 
@@ -873,6 +1047,7 @@
         pw.println(
                 "mMobileDataChangedToEnabledDuringDataStall="
                         + mMobileDataChangedToEnabledDuringDataStall);
+        pw.println("mPredictWaitingMillis=" + mPredictWaitingMillis);
         pw.println(
                 "DataStallRecoveryDelayMillisArray="
                         + Arrays.toString(mDataStallRecoveryDelayMillisArray));
diff --git a/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java b/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java
index 5ed12aa..de8c48c 100644
--- a/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java
+++ b/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java
@@ -315,6 +315,8 @@
         registerNrStateFrequencyChange();
         mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(AccessNetworkConstants
                 .TRANSPORT_TYPE_WWAN, this, MSG_DATA_REG_STATE_OR_RAT_CHANGED, null);
+        mPhone.getSignalStrengthController().registerForSignalStrengthChanged(this,
+                MSG_SIGNAL_STRENGTH_CHANGED, null);
     }
 
     @Override
@@ -333,7 +335,7 @@
                 handleDefaultNetworkChanged((NetworkCapabilities) msg.obj);
                 break;
             case MSG_SIGNAL_STRENGTH_CHANGED:
-                handleSignalStrengthChanged((SignalStrength) msg.obj);
+                handleSignalStrengthChanged();
                 break;
             case MSG_NR_FREQUENCY_CHANGED:
                 // fall through
@@ -917,10 +919,8 @@
                 () -> callback.onBandwidthChanged(linkBandwidthTxKps, linkBandwidthRxKps)));
     }
 
-    private void handleSignalStrengthChanged(SignalStrength signalStrength) {
-        if (signalStrength == null) {
-            return;
-        }
+    private void handleSignalStrengthChanged() {
+        SignalStrength signalStrength = mPhone.getSignalStrength();
 
         mSignalStrengthDbm = signalStrength.getDbm();
         mSignalLevel = signalStrength.getLevel();
@@ -1099,13 +1099,8 @@
     }
 
     private class TelephonyCallbackImpl extends TelephonyCallback implements
-            TelephonyCallback.SignalStrengthsListener,
             TelephonyCallback.ActiveDataSubscriptionIdListener {
         @Override
-        public void onSignalStrengthsChanged(SignalStrength signalStrength) {
-            obtainMessage(MSG_SIGNAL_STRENGTH_CHANGED, signalStrength).sendToTarget();
-        }
-        @Override
         public void onActiveDataSubscriptionIdChanged(int subId) {
             obtainMessage(MSG_ACTIVE_PHONE_CHANGED, subId).sendToTarget();
         }
diff --git a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
index d2fdf4a..cf1a47d 100644
--- a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
+++ b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
@@ -18,6 +18,7 @@
 
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.telephony.CarrierConfigManager.KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG;
+import static android.telephony.SubscriptionManager.DEFAULT_PHONE_INDEX;
 import static android.telephony.SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
 import static android.telephony.SubscriptionManager.INVALID_PHONE_INDEX;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -27,14 +28,10 @@
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM;
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
-
 import static java.util.Arrays.copyOf;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -50,7 +47,6 @@
 import android.net.TelephonyNetworkSpecifier;
 import android.os.AsyncResult;
 import android.os.Build;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -58,10 +54,7 @@
 import android.os.Registrant;
 import android.os.RegistrantList;
 import android.os.RemoteException;
-import android.provider.Settings;
-import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
-import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneCapability;
 import android.telephony.PhoneStateListener;
 import android.telephony.SubscriptionInfo;
@@ -79,6 +72,7 @@
 import com.android.ims.ImsException;
 import com.android.ims.ImsManager;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.ISetOpportunisticDataCallback;
 import com.android.internal.telephony.IccCard;
@@ -96,7 +90,6 @@
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.subscription.SubscriptionManagerService.WatchedInt;
-import com.android.internal.telephony.util.NotificationChannelController;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.telephony.Rlog;
 
@@ -121,21 +114,6 @@
     private static final String LOG_TAG = "PhoneSwitcher";
     protected static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
 
-    /** Fragment "key" argument passed thru {@link #SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS} */
-    private static final String SETTINGS_EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
-    /**
-     * When starting this activity, this extra can also be specified to supply a Bundle of arguments
-     * to pass to that fragment when it is instantiated during the initial creation of the activity.
-     */
-    private static final String SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS =
-            ":settings:show_fragment_args";
-    /** The res Id of the auto data switch fragment in settings. **/
-    private static final String AUTO_DATA_SWITCH_SETTING_R_ID = "auto_data_switch";
-    /** Notification tag **/
-    private static final String AUTO_DATA_SWITCH_NOTIFICATION_TAG = "auto_data_switch";
-    /** Notification ID **/
-    private static final int AUTO_DATA_SWITCH_NOTIFICATION_ID = 1;
-
     private static final int MODEM_COMMAND_RETRY_PERIOD_MS     = 5000;
     // After the emergency call ends, wait for a few seconds to see if we enter ECBM before starting
     // the countdown to remove the emergency DDS override.
@@ -257,9 +235,6 @@
     // its value will be DEFAULT_SUBSCRIPTION_ID.
     private int mAutoSelectedDataSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
 
-    /** The count of consecutive auto switch validation failure **/
-    private int mAutoSwitchRetryFailedCount = 0;
-
     // The phone ID that has an active voice call. If set, and its mobile data setting is on,
     // it will become the mPreferredDataPhoneId.
     protected int mPhoneIdInVoiceCall = SubscriptionManager.INVALID_PHONE_INDEX;
@@ -302,10 +277,10 @@
     // ECBM, which is detected by EVENT_EMERGENCY_TOGGLE.
     private static final int EVENT_PRECISE_CALL_STATE_CHANGED     = 109;
     private static final int EVENT_NETWORK_VALIDATION_DONE        = 110;
-    private static final int EVENT_EVALUATE_AUTO_SWITCH           = 111;
+
     private static final int EVENT_MODEM_COMMAND_DONE             = 112;
     private static final int EVENT_MODEM_COMMAND_RETRY            = 113;
-    private static final int EVENT_SERVICE_STATE_CHANGED          = 114;
+
     // An emergency call is about to be originated and requires the DDS to be overridden.
     // Uses EVENT_PRECISE_CALL_STATE_CHANGED message to start countdown to finish override defined
     // in mEmergencyOverride. If EVENT_PRECISE_CALL_STATE_CHANGED does not come in
@@ -318,7 +293,6 @@
     private static final int EVENT_NETWORK_AVAILABLE              = 118;
     private static final int EVENT_PROCESS_SIM_STATE_CHANGE       = 119;
     private static final int EVENT_IMS_RADIO_TECH_CHANGED         = 120;
-    private static final int EVENT_MEETS_AUTO_DATA_SWITCH_STATE   = 121;
 
     // List of events triggers re-evaluations
     private static final String EVALUATION_REASON_RADIO_ON = "EVENT_RADIO_ON";
@@ -343,23 +317,9 @@
 
     private List<Set<CommandException.Error>> mCurrentDdsSwitchFailure;
 
-    /**
-     * {@code true} if requires ping test before switching preferred data modem; otherwise, switch
-     * even if ping test fails.
-     */
-    private boolean mRequirePingTestBeforeDataSwitch = true;
-
-    /**
-     * Time threshold in ms to define a internet connection status to be stable(e.g. out of service,
-     * in service, wifi is the default active network.etc), while -1 indicates auto switch
-     * feature disabled.
-     */
-    private long mAutoDataSwitchAvailabilityStabilityTimeThreshold = -1;
-
-    /**
-     * The maximum number of retries when a validation for switching failed.
-     */
-    private int mAutoDataSwitchValidationMaxRetry;
+    private AutoDataSwitchController mAutoDataSwitchController;
+    private AutoDataSwitchController.AutoDataSwitchControllerCallback
+            mAutoDataSwitchCallback;
 
     /** Data settings manager callback. Key is the phone id. */
     private final @NonNull Map<Integer, DataSettingsManagerCallback> mDataSettingsManagerCallbacks =
@@ -368,12 +328,10 @@
     private class DefaultNetworkCallback extends ConnectivityManager.NetworkCallback {
         public int mExpectedSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         public int mSwitchReason = TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_UNKNOWN;
-        public boolean isDefaultNetworkOnCellular = false;
         @Override
         public void onCapabilitiesChanged(Network network,
                 NetworkCapabilities networkCapabilities) {
             if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
-                isDefaultNetworkOnCellular = true;
                 if (SubscriptionManager.isValidSubscriptionId(mExpectedSubId)
                         && mExpectedSubId == getSubIdFromNetworkSpecifier(
                         networkCapabilities.getNetworkSpecifier())) {
@@ -384,22 +342,13 @@
                     mExpectedSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
                     mSwitchReason = TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_UNKNOWN;
                 }
-            } else {
-                if (isDefaultNetworkOnCellular) {
-                    // non-cellular transport is active
-                    isDefaultNetworkOnCellular = false;
-                    log("default network is active on non cellular");
-                    evaluateIfAutoSwitchIsNeeded();
-                }
             }
+            mAutoDataSwitchController.updateDefaultNetworkCapabilities(networkCapabilities);
         }
 
         @Override
         public void onLost(Network network) {
-            // try find an active sub to switch to
-            if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) {
-                sendEmptyMessage(EVENT_EVALUATE_AUTO_SWITCH);
-            }
+            mAutoDataSwitchController.updateDefaultNetworkCapabilities(null);
         }
     }
 
@@ -551,8 +500,6 @@
                             }});
                 phone.getDataSettingsManager().registerCallback(
                         mDataSettingsManagerCallbacks.get(phoneId));
-                phone.getServiceStateTracker().registerForServiceStateChanged(this,
-                        EVENT_SERVICE_STATE_CHANGED, phoneId);
                 registerForImsRadioTechChange(context, phoneId);
             }
             Set<CommandException.Error> ddsFailure = new HashSet<CommandException.Error>();
@@ -563,8 +510,6 @@
             PhoneFactory.getPhone(0).mCi.registerForOn(this, EVENT_RADIO_ON, null);
         }
 
-        readDeviceResourceConfig();
-
         TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager)
                 context.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
         telephonyRegistryManager.addOnSubscriptionsChangedListener(
@@ -573,6 +518,36 @@
         mConnectivityManager =
             (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
 
+        mAutoDataSwitchCallback = new AutoDataSwitchController.AutoDataSwitchControllerCallback() {
+            @Override
+            public void onRequireValidation(int targetPhoneId, boolean needValidation) {
+                int targetSubId = targetPhoneId == DEFAULT_PHONE_INDEX
+                        ? DEFAULT_SUBSCRIPTION_ID
+                        : mSubscriptionManagerService.getSubId(targetPhoneId);
+                PhoneSwitcher.this.validate(targetSubId, needValidation,
+                        DataSwitch.Reason.DATA_SWITCH_REASON_AUTO, null);
+            }
+
+            @Override
+            public void onRequireImmediatelySwitchToPhone(int targetPhoneId,
+                    @AutoDataSwitchController.AutoDataSwitchEvaluationReason int reason) {
+                PhoneSwitcher.this.mAutoSelectedDataSubId =
+                        targetPhoneId == DEFAULT_PHONE_INDEX
+                                ? DEFAULT_SUBSCRIPTION_ID
+                                : mSubscriptionManagerService.getSubId(targetPhoneId);
+                PhoneSwitcher.this.evaluateIfImmediateDataSwitchIsNeeded(
+                        AutoDataSwitchController.evaluationReasonToString(reason),
+                        DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL);
+            }
+
+            @Override
+            public void onRequireCancelAnyPendingAutoSwitchValidation() {
+                PhoneSwitcher.this.cancelPendingAutoDataSwitchValidation();
+            }
+        };
+        mAutoDataSwitchController = new AutoDataSwitchController(context, looper, this,
+                mAutoDataSwitchCallback);
+
         mContext.registerReceiver(mDefaultDataChangedReceiver,
                 new IntentFilter(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED));
 
@@ -675,19 +650,6 @@
                 onEvaluate(REQUESTS_UNCHANGED, "subscription changed");
                 break;
             }
-            case EVENT_SERVICE_STATE_CHANGED: {
-                AsyncResult ar = (AsyncResult) msg.obj;
-                final int phoneId = (int) ar.userObj;
-                onServiceStateChanged(phoneId);
-                break;
-            }
-            case EVENT_MEETS_AUTO_DATA_SWITCH_STATE: {
-                final int targetSubId = msg.arg1;
-                final boolean needValidation = (boolean) msg.obj;
-                validate(targetSubId, needValidation,
-                        DataSwitch.Reason.DATA_SWITCH_REASON_AUTO, null);
-                break;
-            }
             case EVENT_PRIMARY_DATA_SUB_CHANGED: {
                 evaluateIfImmediateDataSwitchIsNeeded("primary data sub changed",
                         DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL);
@@ -718,9 +680,6 @@
                 onEvaluate(REQUESTS_CHANGED, "emergencyToggle");
                 break;
             }
-            case EVENT_EVALUATE_AUTO_SWITCH:
-                evaluateIfAutoSwitchIsNeeded();
-                break;
             case EVENT_RADIO_CAPABILITY_CHANGED: {
                 final int phoneId = msg.arg1;
                 sendRilCommands(phoneId);
@@ -792,7 +751,8 @@
                         DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL);
                 if (!isAnyVoiceCallActiveOnDevice()) {
                     // consider auto switch on hang up all voice call
-                    evaluateIfAutoSwitchIsNeeded();
+                    mAutoDataSwitchController.evaluateAutoDataSwitch(
+                            AutoDataSwitchController.EVALUATION_REASON_VOICE_CALL_END);
                 }
                 break;
             }
@@ -892,33 +852,19 @@
                 } else if (TelephonyManager.SIM_STATE_LOADED == simState) {
                     if (mCurrentDdsSwitchFailure.get(slotIndex).contains(
                         CommandException.Error.INVALID_SIM_STATE)
-                        && (TelephonyManager.SIM_STATE_LOADED == simState)
                         && isSimApplicationReady(slotIndex)) {
                         sendRilCommands(slotIndex);
                     }
                     // SIM loaded after subscriptions slot mapping are done. Evaluate for auto
                     // data switch.
-                    sendEmptyMessage(EVENT_EVALUATE_AUTO_SWITCH);
+                    mAutoDataSwitchController.evaluateAutoDataSwitch(
+                            AutoDataSwitchController.EVALUATION_REASON_SIM_LOADED);
                 }
                 break;
             }
         }
     }
 
-    /**
-     * Read the default device config from any default phone because the resource config are per
-     * device. No need to register callback for the same reason.
-     */
-    private void readDeviceResourceConfig() {
-        Phone phone = PhoneFactory.getDefaultPhone();
-        DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager();
-        mRequirePingTestBeforeDataSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired();
-        mAutoDataSwitchAvailabilityStabilityTimeThreshold =
-                dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold();
-        mAutoDataSwitchValidationMaxRetry =
-                dataConfig.getAutoDataSwitchValidationMaxRetry();
-    }
-
     private synchronized void onMultiSimConfigChanged(int activeModemCount) {
         // No change.
         if (mActiveModemCount == activeModemCount) return;
@@ -958,13 +904,13 @@
                     });
             phone.getDataSettingsManager().registerCallback(
                     mDataSettingsManagerCallbacks.get(phone.getPhoneId()));
-            phone.getServiceStateTracker().registerForServiceStateChanged(this,
-                    EVENT_SERVICE_STATE_CHANGED, phoneId);
 
             Set<CommandException.Error> ddsFailure = new HashSet<CommandException.Error>();
             mCurrentDdsSwitchFailure.add(ddsFailure);
             registerForImsRadioTechChange(mContext, phoneId);
         }
+
+        mAutoDataSwitchController.onMultiSimConfigChanged(activeModemCount);
     }
 
     /**
@@ -973,14 +919,14 @@
      * 2. OR user changed auto data switch feature
      */
     private void onDataEnabledChanged() {
-        logl("user changed data related settings");
         if (isAnyVoiceCallActiveOnDevice()) {
             // user changed data related settings during call, switch or turn off immediately
             evaluateIfImmediateDataSwitchIsNeeded(
                     "user changed data settings during call",
                     DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL);
         } else {
-            evaluateIfAutoSwitchIsNeeded();
+            mAutoDataSwitchController.evaluateAutoDataSwitch(AutoDataSwitchController
+                    .EVALUATION_REASON_DATA_SETTINGS_CHANGED);
         }
     }
 
@@ -1069,134 +1015,9 @@
     }
 
     /**
-     * Called when service state changed.
-     */
-    private void onServiceStateChanged(int phoneId) {
-        Phone phone = findPhoneById(phoneId);
-        if (phone != null) {
-            int newRegState = phone.getServiceState()
-                    .getNetworkRegistrationInfo(
-                            NetworkRegistrationInfo.DOMAIN_PS,
-                            AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                    .getRegistrationState();
-            if (newRegState != mPhoneStates[phoneId].dataRegState) {
-                mPhoneStates[phoneId].dataRegState = newRegState;
-                logl("onServiceStateChanged: phoneId:" + phoneId + " dataReg-> "
-                        + NetworkRegistrationInfo.registrationStateToString(newRegState));
-                if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) {
-                    sendEmptyMessage(EVENT_EVALUATE_AUTO_SWITCH);
-                }
-            }
-        }
-    }
-
-    /**
-     * Evaluate if auto switch is suitable at the moment.
-     */
-    private void evaluateIfAutoSwitchIsNeeded() {
-        // auto data switch feature is disabled from server
-        if (mAutoDataSwitchAvailabilityStabilityTimeThreshold < 0) return;
-        // check is valid DSDS
-        if (!isActiveSubId(mPrimaryDataSubId) || mSubscriptionManagerService
-                .getActiveSubIdList(true).length <= 1) {
-            return;
-        }
-
-        Phone primaryDataPhone = getPhoneBySubId(mPrimaryDataSubId);
-        if (primaryDataPhone == null) {
-            loge("evaluateIfAutoSwitchIsNeeded: cannot find primary data phone. subId="
-                    + mPrimaryDataSubId);
-            return;
-        }
-
-        int primaryPhoneId = primaryDataPhone.getPhoneId();
-        log("evaluateIfAutoSwitchIsNeeded: primaryPhoneId: " + primaryPhoneId
-                + " preferredPhoneId: " + mPreferredDataPhoneId);
-        Phone secondaryDataPhone;
-
-        if (mPreferredDataPhoneId == primaryPhoneId) {
-            // on primary data sub
-
-            int candidateSubId = getAutoSwitchTargetSubIdIfExists();
-            if (candidateSubId != INVALID_SUBSCRIPTION_ID) {
-                startAutoDataSwitchStabilityCheck(candidateSubId, mRequirePingTestBeforeDataSwitch);
-            } else {
-                cancelPendingAutoDataSwitch();
-            }
-        } else if ((secondaryDataPhone = findPhoneById(mPreferredDataPhoneId)) != null) {
-            // on secondary data sub
-
-            if (!primaryDataPhone.isUserDataEnabled()
-                    || !secondaryDataPhone.isDataAllowed()) {
-                // immediately switch back if user setting changes
-                mAutoSelectedDataSubId = DEFAULT_SUBSCRIPTION_ID;
-                evaluateIfImmediateDataSwitchIsNeeded("User disabled data settings",
-                        DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL);
-                return;
-            }
-
-            NetworkCapabilities defaultNetworkCapabilities = mConnectivityManager
-                    .getNetworkCapabilities(mConnectivityManager.getActiveNetwork());
-            if (defaultNetworkCapabilities != null && !defaultNetworkCapabilities
-                    .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
-                log("evaluateIfAutoSwitchIsNeeded: "
-                        + "Default network is active on non-cellular transport");
-                startAutoDataSwitchStabilityCheck(DEFAULT_SUBSCRIPTION_ID, false);
-                return;
-            }
-
-            if (mPhoneStates[secondaryDataPhone.getPhoneId()].dataRegState
-                    != NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
-                // secondary phone lost its HOME availability
-                startAutoDataSwitchStabilityCheck(DEFAULT_SUBSCRIPTION_ID, false);
-                return;
-            }
-
-            if (isInService(mPhoneStates[primaryPhoneId])) {
-                // primary becomes available
-                startAutoDataSwitchStabilityCheck(DEFAULT_SUBSCRIPTION_ID,
-                        mRequirePingTestBeforeDataSwitch);
-                return;
-            }
-
-            // cancel any previous attempts of switching back to primary
-            cancelPendingAutoDataSwitch();
-        }
-    }
-
-    /**
-     * @param phoneState The phone state to check
-     * @return {@code true} if the phone state is considered in service.
-     */
-    private boolean isInService(@NonNull PhoneState phoneState) {
-        return phoneState.dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME
-                || phoneState.dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
-    }
-
-    /**
-     * Called when the current environment suits auto data switch.
-     * Start pre-switch validation if the current environment suits auto data switch for
-     * {@link #mAutoDataSwitchAvailabilityStabilityTimeThreshold} MS.
-     * @param targetSubId the target sub Id.
-     * @param needValidation {@code true} if validation is needed.
-     */
-    private void startAutoDataSwitchStabilityCheck(int targetSubId, boolean needValidation) {
-        log("startAutoDataSwitchStabilityCheck: targetSubId=" + targetSubId
-                + " needValidation=" + needValidation);
-        if (!hasMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, needValidation)) {
-            sendMessageDelayed(obtainMessage(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, targetSubId,
-                            0/*placeholder*/,
-                            needValidation),
-                    mAutoDataSwitchAvailabilityStabilityTimeThreshold);
-        }
-    }
-
-    /**
      * Cancel any auto switch attempts when the current environment is not suitable for auto switch.
      */
-    private void cancelPendingAutoDataSwitch() {
-        mAutoSwitchRetryFailedCount = 0;
-        removeMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE);
+    private void cancelPendingAutoDataSwitchValidation() {
         if (mValidator.isValidating()) {
             mValidator.stopValidation();
 
@@ -1207,56 +1028,6 @@
         }
     }
 
-    /**
-     * Called when consider switching from primary default data sub to another data sub.
-     * @return the target subId if a suitable candidate is found, otherwise return
-     * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}
-     */
-    private int getAutoSwitchTargetSubIdIfExists() {
-        Phone primaryDataPhone = getPhoneBySubId(mPrimaryDataSubId);
-        if (primaryDataPhone == null) {
-            log("getAutoSwitchTargetSubId: no sim loaded");
-            return INVALID_SUBSCRIPTION_ID;
-        }
-
-        int primaryPhoneId = primaryDataPhone.getPhoneId();
-
-        if (!primaryDataPhone.isUserDataEnabled()) {
-            log("getAutoSwitchTargetSubId: user disabled data");
-            return INVALID_SUBSCRIPTION_ID;
-        }
-
-        NetworkCapabilities defaultNetworkCapabilities = mConnectivityManager
-                .getNetworkCapabilities(mConnectivityManager.getActiveNetwork());
-        if (defaultNetworkCapabilities != null && !defaultNetworkCapabilities
-                .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
-            // Exists other active default transport
-            log("getAutoSwitchTargetSubId: Default network is active on non-cellular transport");
-            return INVALID_SUBSCRIPTION_ID;
-        }
-
-        // check whether primary and secondary signal status worth switching
-        if (isInService(mPhoneStates[primaryPhoneId])) {
-            log("getAutoSwitchTargetSubId: primary is in service");
-            return INVALID_SUBSCRIPTION_ID;
-        }
-        for (int phoneId = 0; phoneId < mPhoneStates.length; phoneId++) {
-            if (phoneId != primaryPhoneId) {
-                // the alternative phone must have HOME availability
-                if (mPhoneStates[phoneId].dataRegState
-                        == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
-                    log("getAutoSwitchTargetSubId: found phone " + phoneId + " in HOME service");
-                    Phone secondaryDataPhone = findPhoneById(phoneId);
-                    if (secondaryDataPhone != null && // check auto switch feature enabled
-                            secondaryDataPhone.isDataAllowed()) {
-                        return secondaryDataPhone.getSubId();
-                    }
-                }
-            }
-        }
-        return INVALID_SUBSCRIPTION_ID;
-    }
-
     private TelephonyManager getTm() {
         return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
     }
@@ -1415,8 +1186,6 @@
 
     protected static class PhoneState {
         public volatile boolean active = false;
-        public @NetworkRegistrationInfo.RegistrationState int dataRegState =
-                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
         public long lastRequested = 0;
     }
 
@@ -1836,14 +1605,12 @@
         if (!isActiveSubId(subId)) {
             logl("confirmSwitch: subId " + subId + " is no longer active");
             resultForCallBack = SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION;
-            mAutoSwitchRetryFailedCount = 0;
         } else if (!confirm) {
             resultForCallBack = SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED;
 
             // retry for auto data switch validation failure
             if (mLastSwitchPreferredDataReason == DataSwitch.Reason.DATA_SWITCH_REASON_AUTO) {
-                scheduleAutoSwitchRetryEvaluation();
-                mAutoSwitchRetryFailedCount++;
+                mAutoDataSwitchController.evaluateRetryOnValidationFailed();
             }
         } else {
             if (subId == mPrimaryDataSubId) {
@@ -1852,7 +1619,7 @@
                 setAutoSelectedDataSubIdInternal(subId);
             }
             resultForCallBack = SET_OPPORTUNISTIC_SUB_SUCCESS;
-            mAutoSwitchRetryFailedCount = 0;
+            mAutoDataSwitchController.resetFailedCount();
         }
 
         // Trigger callback if needed
@@ -1861,23 +1628,6 @@
         mPendingSwitchSubId = INVALID_SUBSCRIPTION_ID;
     }
 
-    /**
-     * Schedule auto data switch evaluation retry if haven't reached the max retry count.
-     */
-    private void scheduleAutoSwitchRetryEvaluation() {
-        if (mAutoSwitchRetryFailedCount < mAutoDataSwitchValidationMaxRetry) {
-            if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) {
-                sendMessageDelayed(obtainMessage(EVENT_EVALUATE_AUTO_SWITCH),
-                        mAutoDataSwitchAvailabilityStabilityTimeThreshold
-                                << mAutoSwitchRetryFailedCount);
-            }
-        } else {
-            logl("scheduleAutoSwitchEvaluation: reached max auto switch retry count "
-                    + mAutoDataSwitchValidationMaxRetry);
-            mAutoSwitchRetryFailedCount = 0;
-        }
-    }
-
     private void onNetworkAvailable(int subId, Network network) {
         log("onNetworkAvailable: on subId " + subId);
         // Do nothing unless pending switch matches target subId and it doesn't require
@@ -1928,8 +1678,14 @@
         }
 
         // A phone in voice call might trigger data being switched to it.
+        // Exclude dialing to give modem time to process an EMC first before dealing with DDS switch
+        // Include alerting because modem RLF leads to delay in switch, so carrier required to
+        // switch in alerting phase.
+        // TODO: check ringing call for vDADA
         return (!phone.getBackgroundCall().isIdle()
-                || !phone.getForegroundCall().isIdle());
+                && phone.getBackgroundCall().getState() != Call.State.DIALING)
+                || (!phone.getForegroundCall().isIdle()
+                && phone.getForegroundCall().getState() != Call.State.DIALING);
     }
 
     private void updateHalCommandToUse() {
@@ -2062,8 +1818,7 @@
         for (int i = 0; i < mActiveModemCount; i++) {
             PhoneState ps = mPhoneStates[i];
             c.setTimeInMillis(ps.lastRequested);
-            pw.println("PhoneId(" + i + ") active=" + ps.active + ", dataRegState="
-                    + NetworkRegistrationInfo.registrationStateToString(ps.dataRegState)
+            pw.println("PhoneId(" + i + ") active=" + ps.active
                     + ", lastRequest="
                     + (ps.lastRequested == 0 ? "never" :
                      String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)));
@@ -2081,10 +1836,6 @@
         pw.println("mActiveModemCount=" + mActiveModemCount);
         pw.println("mPhoneIdInVoiceCall=" + mPhoneIdInVoiceCall);
         pw.println("mCurrentDdsSwitchFailure=" + mCurrentDdsSwitchFailure);
-        pw.println("mAutoDataSwitchAvailabilityStabilityTimeThreshold="
-                + mAutoDataSwitchAvailabilityStabilityTimeThreshold);
-        pw.println("mAutoDataSwitchValidationMaxRetry=" + mAutoDataSwitchValidationMaxRetry);
-        pw.println("mRequirePingTestBeforeDataSwitch=" + mRequirePingTestBeforeDataSwitch);
         pw.println("mLastSwitchPreferredDataReason="
                 + switchReasonToString(mLastSwitchPreferredDataReason));
         pw.println("mDisplayedAutoSwitchNotification=" + mDisplayedAutoSwitchNotification);
@@ -2092,6 +1843,7 @@
         pw.increaseIndent();
         mLocalLog.dump(fd, pw, args);
         pw.decreaseIndent();
+        mAutoDataSwitchController.dump(fd, pw, args);
         pw.decreaseIndent();
     }
 
@@ -2130,72 +1882,15 @@
                         phoneId), MODEM_COMMAND_RETRY_PERIOD_MS);
             return;
         }
-        if (commandSuccess) logl("onDdsSwitchResponse: DDS switch success on phoneId = " + phoneId);
+        if (commandSuccess) {
+            logl("onDdsSwitchResponse: DDS switch success on phoneId = " + phoneId);
+            mAutoDataSwitchController.displayAutoDataSwitchNotification(phoneId,
+                    mLastSwitchPreferredDataReason == DataSwitch.Reason.DATA_SWITCH_REASON_AUTO);
+        }
         mCurrentDdsSwitchFailure.get(phoneId).clear();
         // Notify all registrants
         mActivePhoneRegistrants.notifyRegistrants();
         notifyPreferredDataSubIdChanged();
-        displayAutoDataSwitchNotification();
-    }
-
-    /**
-     * Display a notification the first time auto data switch occurs.
-     */
-    private void displayAutoDataSwitchNotification() {
-        NotificationManager notificationManager = (NotificationManager)
-                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-
-        if (mDisplayedAutoSwitchNotification) {
-            // cancel posted notification if any exist
-            log("displayAutoDataSwitchNotification: canceling any notifications for subId "
-                    + mAutoSelectedDataSubId);
-            notificationManager.cancel(AUTO_DATA_SWITCH_NOTIFICATION_TAG,
-                    AUTO_DATA_SWITCH_NOTIFICATION_ID);
-            return;
-        }
-        // proceed only the first time auto data switch occurs, which includes data during call
-        if (mLastSwitchPreferredDataReason != DataSwitch.Reason.DATA_SWITCH_REASON_AUTO) {
-            log("displayAutoDataSwitchNotification: Ignore DDS switch due to "
-                    + switchReasonToString(mLastSwitchPreferredDataReason));
-            return;
-        }
-        SubscriptionInfo subInfo = mSubscriptionManagerService
-                .getSubscriptionInfo(mAutoSelectedDataSubId);
-        if (subInfo == null || subInfo.isOpportunistic()) {
-            loge("displayAutoDataSwitchNotification: mAutoSelectedDataSubId="
-                    + mAutoSelectedDataSubId + " unexpected subInfo " + subInfo);
-            return;
-        }
-        logl("displayAutoDataSwitchNotification: display for subId=" + mAutoSelectedDataSubId);
-        // "Mobile network settings" screen / dialog
-        Intent intent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS);
-        final Bundle fragmentArgs = new Bundle();
-        // Special contract for Settings to highlight permission row
-        fragmentArgs.putString(SETTINGS_EXTRA_FRAGMENT_ARG_KEY, AUTO_DATA_SWITCH_SETTING_R_ID);
-        intent.putExtra(Settings.EXTRA_SUB_ID, mAutoSelectedDataSubId);
-        intent.putExtra(SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
-        PendingIntent contentIntent = PendingIntent.getActivity(
-                mContext, mAutoSelectedDataSubId, intent, PendingIntent.FLAG_IMMUTABLE);
-
-        CharSequence activeCarrierName = subInfo.getDisplayName();
-        CharSequence contentTitle = mContext.getString(
-                com.android.internal.R.string.auto_data_switch_title, activeCarrierName);
-        CharSequence contentText = mContext.getText(
-                com.android.internal.R.string.auto_data_switch_content);
-
-        final Notification notif = new Notification.Builder(mContext)
-                .setContentTitle(contentTitle)
-                .setContentText(contentText)
-                .setSmallIcon(android.R.drawable.stat_sys_warning)
-                .setColor(mContext.getResources().getColor(
-                        com.android.internal.R.color.system_notification_accent_color))
-                .setChannelId(NotificationChannelController.CHANNEL_ID_MOBILE_DATA_STATUS)
-                .setContentIntent(contentIntent)
-                .setStyle(new Notification.BigTextStyle().bigText(contentText))
-                .build();
-        notificationManager.notify(AUTO_DATA_SWITCH_NOTIFICATION_TAG,
-                AUTO_DATA_SWITCH_NOTIFICATION_ID, notif);
-        mDisplayedAutoSwitchNotification = true;
     }
 
     private boolean isPhoneIdValidForRetry(int phoneId) {
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccController.java b/src/java/com/android/internal/telephony/euicc/EuiccController.java
index a5b95c3..c8f3368 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java
@@ -191,6 +191,19 @@
             boolean usePortIndex = resolutionIntent.getBooleanExtra(
                     EuiccService.EXTRA_RESOLUTION_USE_PORT_INDEX, false);
             resolutionExtras.putBoolean(EuiccService.EXTRA_RESOLUTION_USE_PORT_INDEX, usePortIndex);
+
+            if (!EuiccService.ACTION_RESOLVE_NO_PRIVILEGES.equals(resolutionIntent.getAction())
+                    || !resolutionExtras.containsKey(EuiccService.EXTRA_RESOLUTION_PORT_INDEX)) {
+                // Port index resolution is requested only through the ACTION_RESOLVE_NO_PRIVILEGES
+                // action. Therefore, if the action is not ACTION_RESOLVE_NO_PRIVILEGES, use the
+                // port index from the resolution intent.
+                // (OR) If the action is ACTION_RESOLVE_NO_PRIVILEGES and resolutionExtras does not
+                // contain the EXTRA_RESOLUTION_PORT_INDEX key, retrieve the port index from
+                // resolutionIntent.
+                resolutionExtras.putInt(EuiccService.EXTRA_RESOLUTION_PORT_INDEX,
+                        resolutionIntent.getIntExtra(EuiccService.EXTRA_RESOLUTION_PORT_INDEX,
+                                TelephonyManager.DEFAULT_PORT_INDEX));
+            }
             Log.i(TAG, " continueOperation portIndex: " + resolutionExtras.getInt(
                     EuiccService.EXTRA_RESOLUTION_PORT_INDEX) + " usePortIndex: " + usePortIndex);
             op.continueOperation(cardId, resolutionExtras, callbackIntent);
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index 4b150cc..3ab9fd7 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -139,6 +139,7 @@
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
+import java.util.stream.Stream;
 
 /**
  * {@hide}
@@ -2539,10 +2540,6 @@
     /** Sets the IMS phone number from IMS associated URIs, if any found. */
     @VisibleForTesting
     public void setPhoneNumberForSourceIms(Uri[] uris) {
-        String phoneNumber = extractPhoneNumberFromAssociatedUris(uris);
-        if (phoneNumber == null) {
-            return;
-        }
         int subId = getSubId();
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
             // Defending b/219080264:
@@ -2551,16 +2548,33 @@
             // IMS callbacks are sent back to telephony after SIM state changed.
             return;
         }
-
         SubscriptionInfoInternal subInfo = mSubscriptionManagerService
                 .getSubscriptionInfoInternal(subId);
-        if (subInfo != null) {
-            phoneNumber = PhoneNumberUtils.formatNumberToE164(phoneNumber,
-                    subInfo.getCountryIso());
+        if (subInfo == null) {
+            loge("trigger setPhoneNumberForSourceIms, but subInfo is null");
+            return;
+        }
+        String subCountryIso = subInfo.getCountryIso();
+        String phoneNumber = extractPhoneNumberFromAssociatedUris(uris, /*isGlobalFormat*/true);
+        if (phoneNumber != null) {
+            phoneNumber = PhoneNumberUtils.formatNumberToE164(phoneNumber, subCountryIso);
             if (phoneNumber == null) {
+                loge("format to E164 failed");
                 return;
             }
             mSubscriptionManagerService.setNumberFromIms(subId, phoneNumber);
+        } else if (isAllowNonGlobalNumberFormat()) {
+            // If carrier config has true for KEY_IGNORE_GLOBAL_PHONE_NUMBER_FORMAT_BOOL and
+            // P-Associated-Uri does not have global number,
+            // try to find phone number excluding '+' one more time.
+            phoneNumber = extractPhoneNumberFromAssociatedUris(uris, /*isGlobalFormat*/false);
+            if (phoneNumber == null) {
+                loge("extract phone number without '+' failed");
+                return;
+            }
+            mSubscriptionManagerService.setNumberFromIms(subId, phoneNumber);
+        } else {
+            logd("extract phone number failed");
         }
     }
 
@@ -2570,24 +2584,40 @@
      * <p>Associated URIs are public user identities, and phone number could be used:
      * see 3GPP TS 24.229 5.4.1.2 and 3GPP TS 23.003 13.4. This algotihm look for the
      * possible "global number" in E.164 format.
+     * <p>If true try finding phone number even if the P-Associated-Uri does not have global
+     * number format.
      */
-    private static String extractPhoneNumberFromAssociatedUris(Uri[] uris) {
+    private static String extractPhoneNumberFromAssociatedUris(Uri[] uris, boolean isGlobalFormat) {
         if (uris == null) {
             return null;
         }
-        return Arrays.stream(uris)
+
+        Stream<String> intermediate = Arrays.stream(uris)
                 // Phone number is an opaque URI "tel:<phone-number>" or "sip:<phone-number>@<...>"
                 .filter(u -> u != null && u.isOpaque())
                 .filter(u -> "tel".equalsIgnoreCase(u.getScheme())
                         || "sip".equalsIgnoreCase(u.getScheme()))
-                .map(Uri::getSchemeSpecificPart)
-                // "Global number" should be in E.164 format starting with "+" e.g. "+447539447777"
-                .filter(ssp -> ssp != null && ssp.startsWith("+"))
-                // Remove whatever after "@" for sip URI
-                .map(ssp -> ssp.split("@")[0])
-                // Returns the first winner
-                .findFirst()
-                .orElse(null);
+                .map(Uri::getSchemeSpecificPart);
+
+        if (isGlobalFormat) {
+            // "Global number" should be in E.164 format starting with "+" e.g. "+447539447777"
+            return intermediate.filter(ssp -> ssp != null && ssp.startsWith("+"))
+                    // Remove whatever after "@" for sip URI
+                    .map(ssp -> ssp.split("@")[0])
+                    // Returns the first winner
+                    .findFirst()
+                    .orElse(null);
+        } else {
+            // non global number format
+            return intermediate.filter(ssp -> ssp != null)
+                    // Remove whatever after "@" for sip URI
+                    .map(ssp -> ssp.split("@")[0])
+                    // regular expression, allow only number
+                    .filter(ssp -> ssp.matches("^[0-9]+$"))
+                    // Returns the first winner
+                    .findFirst()
+                    .orElse(null);
+        }
     }
 
     public IccRecords getIccRecords() {
@@ -2790,6 +2820,22 @@
         pw.flush();
     }
 
+    private boolean isAllowNonGlobalNumberFormat() {
+        PersistableBundle persistableBundle = null;
+        CarrierConfigManager carrierConfigManager = (CarrierConfigManager) mContext
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (carrierConfigManager != null) {
+            persistableBundle = carrierConfigManager.getConfigForSubId(getSubId(),
+                    CarrierConfigManager.Ims.KEY_ALLOW_NON_GLOBAL_PHONE_NUMBER_FORMAT_BOOL);
+        }
+        if (persistableBundle != null) {
+            return persistableBundle.getBoolean(
+                    CarrierConfigManager.Ims.KEY_ALLOW_NON_GLOBAL_PHONE_NUMBER_FORMAT_BOOL, false);
+        }
+
+        return false;
+    }
+
     private void logi(String s) {
         Rlog.i(LOG_TAG, "[" + mPhoneId + "] " + s);
     }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
old mode 100644
new mode 100755
index b984d84..104c925
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
@@ -82,7 +82,7 @@
     private ImsPhoneCall mParent;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private ImsCall mImsCall;
-    private Bundle mExtras = new Bundle();
+    private final Bundle mExtras = new Bundle();
     private TelephonyMetrics mMetrics = TelephonyMetrics.getInstance();
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1491,7 +1491,10 @@
      * @return boolean: true if cross sim calling, false otherwise
      */
     public boolean isCrossSimCall() {
-        return mImsCall != null && mImsCall.isCrossSimCall();
+        if (mImsCall == null) {
+            return mExtras.getBoolean(ImsCallProfile.EXTRA_IS_CROSS_SIM_CALL);
+        }
+        return mImsCall.isCrossSimCall();
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java
index cfa16d0..afb87dd 100644
--- a/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java
+++ b/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java
@@ -24,6 +24,7 @@
 import android.telephony.Annotation.DataFailureCause;
 import android.telephony.Annotation.NetworkType;
 import android.telephony.DataFailCause;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -319,7 +320,7 @@
         ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker();
         ServiceState serviceState =
                 serviceStateTracker != null ? serviceStateTracker.getServiceState() : null;
-        return serviceState != null && serviceState.getRoaming();
+        return ServiceStateStats.isNetworkRoaming(serviceState, NetworkRegistrationInfo.DOMAIN_PS);
     }
 
     private boolean getIsOpportunistic() {
diff --git a/src/java/com/android/internal/telephony/metrics/DataConnectionStateTracker.java b/src/java/com/android/internal/telephony/metrics/DataConnectionStateTracker.java
new file mode 100644
index 0000000..c7ef625
--- /dev/null
+++ b/src/java/com/android/internal/telephony/metrics/DataConnectionStateTracker.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2023 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.internal.telephony.metrics;
+
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
+import android.telephony.PreciseDataConnectionState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.Phone;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ *  This Tracker is monitoring precise data connection states for each APNs which are used for IMS
+ * calling such as IMS and Emergency APN. It uses a SparseArray to track each SIM's connection
+ * state.
+ *  The tracker is started by {@link VoiceCallSessionStats} and update the states to
+ * VoiceCallSessionStats directly.
+ */
+public class DataConnectionStateTracker {
+    private static final SparseArray<DataConnectionStateTracker> sDataConnectionStateTracker =
+            new SparseArray<>();
+    private final Executor mExecutor;
+    private Phone mPhone;
+    private int mSubId;
+    private HashMap<Integer, PreciseDataConnectionState> mLastPreciseDataConnectionState =
+            new HashMap<>();
+    private PreciseDataConnectionStateListenerImpl mDataConnectionStateListener;
+
+    private final SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionsChangedListener =
+            new SubscriptionManager.OnSubscriptionsChangedListener() {
+                @Override
+                public void onSubscriptionsChanged() {
+                    if (mPhone == null) {
+                        return;
+                    }
+                    int newSubId = mPhone.getSubId();
+                    if (mSubId == newSubId
+                            || newSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                        return;
+                    }
+
+                    unregisterTelephonyListener();
+                    mSubId = newSubId;
+                    registerTelephonyListener(mSubId);
+                }
+            };
+
+    private DataConnectionStateTracker() {
+        HandlerThread handlerThread =
+                new HandlerThread(DataConnectionStateTracker.class.getSimpleName());
+        handlerThread.start();
+        mExecutor = new HandlerExecutor(new Handler(handlerThread.getLooper()));
+    }
+
+    /** Getting or Creating DataConnectionStateTracker based on phoneId */
+    public static synchronized DataConnectionStateTracker getInstance(int phoneId) {
+        DataConnectionStateTracker dataConnectionStateTracker =
+                sDataConnectionStateTracker.get(phoneId);
+        if (dataConnectionStateTracker != null) {
+            return dataConnectionStateTracker;
+        }
+
+        dataConnectionStateTracker = new DataConnectionStateTracker();
+        sDataConnectionStateTracker.put(phoneId, dataConnectionStateTracker);
+        return dataConnectionStateTracker;
+    }
+
+    /** Starting to monitor the precise data connection states */
+    public void start(Phone phone) {
+        mPhone = phone;
+        mSubId = mPhone.getSubId();
+        registerTelephonyListener(mSubId);
+        SubscriptionManager mSubscriptionManager = mPhone.getContext()
+                .getSystemService(SubscriptionManager.class);
+        if (mSubscriptionManager != null) {
+            mSubscriptionManager
+                    .addOnSubscriptionsChangedListener(mExecutor, mSubscriptionsChangedListener);
+        }
+    }
+
+    /** Stopping monitoring for the precise data connection states */
+    public void stop() {
+        if (mPhone == null) {
+            return;
+        }
+        SubscriptionManager mSubscriptionManager = mPhone.getContext()
+                .getSystemService(SubscriptionManager.class);
+        if (mSubscriptionManager != null) {
+            mSubscriptionManager
+                    .removeOnSubscriptionsChangedListener(mSubscriptionsChangedListener);
+        }
+        unregisterTelephonyListener();
+        mPhone = null;
+        mLastPreciseDataConnectionState.clear();
+    }
+
+    /** Returns data state of the last notified precise data connection state for apn type */
+    public int getDataState(int apnType) {
+        if (!mLastPreciseDataConnectionState.containsKey(apnType)) {
+            return TelephonyManager.DATA_UNKNOWN;
+        }
+        return mLastPreciseDataConnectionState.get(apnType).getState();
+    }
+
+    private void registerTelephonyListener(int subId) {
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return;
+        }
+        TelephonyManager telephonyManager =
+                mPhone.getContext().getSystemService(TelephonyManager.class);
+        if (telephonyManager != null) {
+            mDataConnectionStateListener = new PreciseDataConnectionStateListenerImpl(mExecutor);
+            mDataConnectionStateListener.register(telephonyManager.createForSubscriptionId(subId));
+        }
+    }
+
+    private void unregisterTelephonyListener() {
+        if (mDataConnectionStateListener != null) {
+            mDataConnectionStateListener.unregister();
+            mDataConnectionStateListener = null;
+        }
+    }
+
+    @VisibleForTesting
+    public void notifyDataConnectionStateChanged(PreciseDataConnectionState connectionState) {
+        List<Integer> apnTypes = connectionState.getApnSetting().getApnTypes();
+        if (apnTypes != null) {
+            for (int apnType : apnTypes) {
+                mLastPreciseDataConnectionState.put(apnType, connectionState);
+            }
+        }
+
+        mPhone.getVoiceCallSessionStats().onPreciseDataConnectionStateChanged(connectionState);
+    }
+
+    private class PreciseDataConnectionStateListenerImpl extends TelephonyCallback
+            implements TelephonyCallback.PreciseDataConnectionStateListener {
+        private final Executor mExecutor;
+        private TelephonyManager mTelephonyManager = null;
+
+        PreciseDataConnectionStateListenerImpl(Executor executor) {
+            mExecutor = executor;
+        }
+
+        public void register(TelephonyManager tm) {
+            if (tm == null) {
+                return;
+            }
+            mTelephonyManager = tm;
+            mTelephonyManager.registerTelephonyCallback(mExecutor, this);
+        }
+
+        public void unregister() {
+            if (mTelephonyManager != null) {
+                mTelephonyManager.unregisterTelephonyCallback(this);
+                mTelephonyManager = null;
+            }
+        }
+
+        @Override
+        public void onPreciseDataConnectionStateChanged(
+                PreciseDataConnectionState connectionState) {
+            notifyDataConnectionStateChanged(connectionState);
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java b/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
index 2f22196..0fd97ba 100644
--- a/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
+++ b/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
@@ -16,138 +16,376 @@
 
 package com.android.internal.telephony.metrics;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.NetworkCapabilities;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation.NetworkType;
 import android.telephony.CellSignalStrength;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
+import android.telephony.data.DataCallResponse;
+import android.telephony.data.DataCallResponse.LinkStatus;
 
 import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.TelephonyStatsLog;
+import com.android.internal.telephony.data.DataNetwork;
+import com.android.internal.telephony.data.DataNetworkController;
+import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
 import com.android.internal.telephony.data.DataStallRecoveryManager;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
+import com.android.telephony.Rlog;
 
-/** Generates metrics related to data stall recovery events per phone ID for the pushed atom. */
+import java.util.List;
+
+/**
+ * Generates metrics related to data stall recovery events per phone ID for the pushed atom.
+ */
 public class DataStallRecoveryStats {
-    /**
-     * Create and push new atom when there is a data stall recovery event
-     *
-     * @param recoveryAction Data stall recovery action
-     * @param phone
-     */
-
-    /* Since the Enum has been extended in Android T, we are mapping it to the correct number. */
-    private static final int RECOVERY_ACTION_RADIO_RESTART_MAPPING = 3;
-    private static final int RECOVERY_ACTION_RESET_MODEM_MAPPING = 4;
 
     /**
-     * Called when data stall happened.
-     *
-     * @param recoveryAction The recovery action.
-     * @param phone The phone instance.
-     * @param isRecovered The data stall symptom recovered or not.
-     * @param durationMillis The duration from data stall symptom occurred.
-     * @param reason The recovered(data resume) reason.
-     * @param isFirstValidation The validation status if it's the first come after recovery.
+     * Value indicating that link bandwidth is unspecified.
+     * Copied from {@code NetworkCapabilities#LINK_BANDWIDTH_UNSPECIFIED}
      */
-    public static void onDataStallEvent(
-            @DataStallRecoveryManager.RecoveryAction int recoveryAction,
-            Phone phone,
+    private static final int LINK_BANDWIDTH_UNSPECIFIED = 0;
+
+    private static final String TAG = "DSRS-";
+
+    // Handler to upload metrics.
+    private final @NonNull Handler mHandler;
+
+    private final @NonNull String mTag;
+    private final @NonNull Phone mPhone;
+
+    // The interface name of the internet network.
+    private @Nullable String mIfaceName = null;
+
+    /* Metrics and stats data variables */
+    private int mPhoneId = 0;
+    private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+    private int mSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+    private int mBand = 0;
+    // The RAT used for data (including IWLAN).
+    private @NetworkType int mRat = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+    private boolean mIsOpportunistic = false;
+    private boolean mIsMultiSim = false;
+    private int mNetworkRegState = NetworkRegistrationInfo
+                    .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
+    // Info of the other device in case of DSDS
+    private int mOtherSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+    private int mOtherNetworkRegState = NetworkRegistrationInfo
+                    .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
+    // Link status of the data network
+    private @LinkStatus int mInternetLinkStatus = DataCallResponse.LINK_STATUS_UNKNOWN;
+
+    // The link bandwidth of the data network
+    private int mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
+    private int mLinkUpBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
+
+    /**
+     * Constructs a new instance of {@link DataStallRecoveryStats}.
+     */
+    public DataStallRecoveryStats(@NonNull final Phone phone,
+            @NonNull final DataNetworkController dataNetworkController) {
+        mTag = TAG + phone.getPhoneId();
+        mPhone = phone;
+
+        HandlerThread handlerThread = new HandlerThread(mTag + "-thread");
+        handlerThread.start();
+        mHandler = new Handler(handlerThread.getLooper());
+
+        dataNetworkController.registerDataNetworkControllerCallback(
+                new DataNetworkControllerCallback(mHandler::post) {
+                @Override
+                public void onInternetDataNetworkConnected(
+                        @NonNull List<DataNetwork> internetNetworks) {
+                    for (DataNetwork dataNetwork : internetNetworks) {
+                        mIfaceName = dataNetwork.getLinkProperties().getInterfaceName();
+                        break;
+                    }
+                }
+
+                @Override
+                public void onInternetDataNetworkDisconnected() {
+                    mIfaceName = null;
+                }
+
+                @Override
+                public void onPhysicalLinkStatusChanged(@LinkStatus int status) {
+                    mInternetLinkStatus = status;
+                }
+            });
+    }
+
+    /**
+     * Create and push new atom when there is a data stall recovery event.
+     *
+     * @param action The recovery action.
+     * @param isRecovered Whether the data stall has been recovered.
+     * @param duration The duration from data stall occurred in milliseconds.
+     * @param reason The reason for the recovery.
+     * @param isFirstValidation Whether this is the first validation after recovery.
+     * @param durationOfAction The duration of the current action in milliseconds.
+     */
+    public void uploadMetrics(
+            @DataStallRecoveryManager.RecoveryAction int action,
             boolean isRecovered,
-            int durationMillis,
+            int duration,
             @DataStallRecoveryManager.RecoveredReason int reason,
             boolean isFirstValidation,
-            int durationMillisOfCurrentAction) {
-        if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
-            phone = phone.getDefaultPhone();
+            int durationOfAction) {
+
+        mHandler.post(() -> {
+            // Update data stall stats
+            log("Set recovery action to " + action);
+
+            // Refreshes the metrics data.
+            try {
+                refreshMetricsData();
+            } catch (Exception e) {
+                loge("The metrics data cannot be refreshed.", e);
+                return;
+            }
+
+            TelephonyStatsLog.write(
+                    TelephonyStatsLog.DATA_STALL_RECOVERY_REPORTED,
+                    mCarrierId,
+                    mRat,
+                    mSignalStrength,
+                    action,
+                    mIsOpportunistic,
+                    mIsMultiSim,
+                    mBand,
+                    isRecovered,
+                    duration,
+                    reason,
+                    mOtherSignalStrength,
+                    mOtherNetworkRegState,
+                    mNetworkRegState,
+                    isFirstValidation,
+                    mPhoneId,
+                    durationOfAction,
+                    mInternetLinkStatus,
+                    mLinkUpBandwidthKbps,
+                    mLinkDownBandwidthKbps);
+
+            log("Upload stats: "
+                    + "Action:"
+                    + action
+                    + ", Recovered:"
+                    + isRecovered
+                    + ", Duration:"
+                    + duration
+                    + ", Reason:"
+                    + reason
+                    + ", First validation:"
+                    + isFirstValidation
+                    + ", Duration of action:"
+                    + durationOfAction
+                    + ", "
+                    + this);
+        });
+    }
+
+    /**
+     * Refreshes the metrics data.
+     */
+    private void refreshMetricsData() {
+        logd("Refreshes the metrics data.");
+        // Update phone id/carrier id and signal strength
+        mPhoneId = mPhone.getPhoneId() + 1;
+        mCarrierId = mPhone.getCarrierId();
+        mSignalStrength = mPhone.getSignalStrength().getLevel();
+
+        // Update the bandwidth.
+        updateBandwidths();
+
+        // Update the RAT and band.
+        updateRatAndBand();
+
+        // Update the opportunistic state.
+        mIsOpportunistic = getIsOpportunistic(mPhone);
+
+        // Update the multi-SIM state.
+        mIsMultiSim = SimSlotState.getCurrentState().numActiveSims > 1;
+
+        // Update the network registration state.
+        updateNetworkRegState();
+
+        // Update the DSDS information.
+        updateDsdsInfo();
+    }
+
+    /**
+     * Updates the bandwidth for the current data network.
+     */
+    private void updateBandwidths() {
+        mLinkDownBandwidthKbps = mLinkUpBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
+
+        if (mIfaceName == null) {
+            loge("Interface name is null");
+            return;
         }
 
-        int carrierId = phone.getCarrierId();
-        int rat = getRat(phone);
-        int band =
-                (rat == TelephonyManager.NETWORK_TYPE_IWLAN) ? 0 : ServiceStateStats.getBand(phone);
-        // the number returned here matches the SignalStrength enum we have
-        int signalStrength = phone.getSignalStrength().getLevel();
-        boolean isOpportunistic = getIsOpportunistic(phone);
-        boolean isMultiSim = SimSlotState.getCurrentState().numActiveSims > 1;
-
-        if (recoveryAction == DataStallRecoveryManager.RECOVERY_ACTION_RADIO_RESTART) {
-            recoveryAction = RECOVERY_ACTION_RADIO_RESTART_MAPPING;
-        } else if (recoveryAction == DataStallRecoveryManager.RECOVERY_ACTION_RESET_MODEM) {
-            recoveryAction = RECOVERY_ACTION_RESET_MODEM_MAPPING;
+        DataNetworkController dataNetworkController = mPhone.getDataNetworkController();
+        if (dataNetworkController == null) {
+            loge("DataNetworkController is null");
+            return;
         }
 
-        // collect info of the other device in case of DSDS
-        int otherSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
-        // the number returned here matches the NetworkRegistrationState enum we have
-        int otherNetworkRegState = NetworkRegistrationInfo
-                .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
+        DataNetwork dataNetwork = dataNetworkController.getDataNetworkByInterface(mIfaceName);
+        if (dataNetwork == null) {
+            loge("DataNetwork is null");
+            return;
+        }
+        NetworkCapabilities networkCapabilities = dataNetwork.getNetworkCapabilities();
+        if (networkCapabilities == null) {
+            loge("NetworkCapabilities is null");
+            return;
+        }
+
+        mLinkDownBandwidthKbps = networkCapabilities.getLinkDownstreamBandwidthKbps();
+        mLinkUpBandwidthKbps = networkCapabilities.getLinkUpstreamBandwidthKbps();
+    }
+
+    private void updateRatAndBand() {
+        mRat = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        mBand = 0;
+        ServiceState serviceState = mPhone.getServiceState();
+        if (serviceState == null) {
+            loge("ServiceState is null");
+            return;
+        }
+
+        mRat = serviceState.getDataNetworkType();
+        mBand =
+            (mRat == TelephonyManager.NETWORK_TYPE_IWLAN) ? 0 : ServiceStateStats.getBand(mPhone);
+    }
+
+    private static boolean getIsOpportunistic(@NonNull Phone phone) {
+        SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
+                .getSubscriptionInfoInternal(phone.getSubId());
+        return subInfo != null && subInfo.isOpportunistic();
+    }
+
+    private void updateNetworkRegState() {
+        mNetworkRegState = NetworkRegistrationInfo
+            .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
+
+        NetworkRegistrationInfo phoneRegInfo = mPhone.getServiceState()
+                .getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        if (phoneRegInfo != null) {
+            mNetworkRegState = phoneRegInfo.getRegistrationState();
+        }
+    }
+
+    private void updateDsdsInfo() {
+        mOtherSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+        mOtherNetworkRegState = NetworkRegistrationInfo
+            .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
         for (Phone otherPhone : PhoneFactory.getPhones()) {
-            if (otherPhone.getPhoneId() == phone.getPhoneId()) continue;
+            if (otherPhone.getPhoneId() == mPhone.getPhoneId()) continue;
             if (!getIsOpportunistic(otherPhone)) {
-                otherSignalStrength = otherPhone.getSignalStrength().getLevel();
+                mOtherSignalStrength = otherPhone.getSignalStrength().getLevel();
                 NetworkRegistrationInfo regInfo = otherPhone.getServiceState()
                         .getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
-                                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
                 if (regInfo != null) {
-                    otherNetworkRegState = regInfo.getRegistrationState();
+                    mOtherNetworkRegState = regInfo.getRegistrationState();
                 }
                 break;
             }
         }
-
-        // the number returned here matches the NetworkRegistrationState enum we have
-        int phoneNetworkRegState = NetworkRegistrationInfo
-                .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
-
-        NetworkRegistrationInfo phoneRegInfo = phone.getServiceState()
-                        .getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
-                                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-        if (phoneRegInfo != null) {
-            phoneNetworkRegState = phoneRegInfo.getRegistrationState();
-        }
-
-        // reserve 0 for default value
-        int phoneId = phone.getPhoneId() + 1;
-
-        TelephonyStatsLog.write(
-                TelephonyStatsLog.DATA_STALL_RECOVERY_REPORTED,
-                carrierId,
-                rat,
-                signalStrength,
-                recoveryAction,
-                isOpportunistic,
-                isMultiSim,
-                band,
-                isRecovered,
-                durationMillis,
-                reason,
-                otherSignalStrength,
-                otherNetworkRegState,
-                phoneNetworkRegState,
-                isFirstValidation,
-                phoneId,
-                durationMillisOfCurrentAction);
     }
 
-    /** Returns the RAT used for data (including IWLAN). */
-    private static @NetworkType int getRat(Phone phone) {
-        ServiceStateTracker serviceStateTracker = phone.getServiceStateTracker();
-        ServiceState serviceState =
-                serviceStateTracker != null ? serviceStateTracker.getServiceState() : null;
-        return serviceState != null
-                ? serviceState.getDataNetworkType()
-                : TelephonyManager.NETWORK_TYPE_UNKNOWN;
+    /**
+     * Return bundled data stall recovery metrics data.
+     *
+     * @param action The recovery action.
+     * @param isRecovered Whether the data stall has been recovered.
+     * @param duration The duration from data stall occurred in milliseconds.
+     * @param reason The reason for the recovery.
+     * @param isFirstValidation Whether this is the first validation after recovery.
+     * @param durationOfAction The duration of the current action in milliseconds.
+     */
+    public Bundle getDataStallRecoveryMetricsData(
+            @DataStallRecoveryManager.RecoveryAction int action,
+            boolean isRecovered,
+            int duration,
+            @DataStallRecoveryManager.RecoveredReason int reason,
+            boolean isFirstValidation,
+            int durationOfAction) {
+        Bundle bundle = new Bundle();
+        bundle.putInt("Action", action);
+        bundle.putBoolean("IsRecovered", isRecovered);
+        bundle.putInt("Duration", duration);
+        bundle.putInt("Reason", reason);
+        bundle.putBoolean("IsFirstValidation", isFirstValidation);
+        bundle.putInt("DurationOfAction", durationOfAction);
+        bundle.putInt("PhoneId", mPhoneId);
+        bundle.putInt("CarrierId", mCarrierId);
+        bundle.putInt("SignalStrength", mSignalStrength);
+        bundle.putInt("Band", mBand);
+        bundle.putInt("Rat", mRat);
+        bundle.putBoolean("IsOpportunistic", mIsOpportunistic);
+        bundle.putBoolean("IsMultiSim", mIsMultiSim);
+        bundle.putInt("NetworkRegState", mNetworkRegState);
+        bundle.putInt("OtherSignalStrength", mOtherSignalStrength);
+        bundle.putInt("OtherNetworkRegState", mOtherNetworkRegState);
+        bundle.putInt("InternetLinkStatus", mInternetLinkStatus);
+        bundle.putInt("LinkDownBandwidthKbps", mLinkDownBandwidthKbps);
+        bundle.putInt("LinkUpBandwidthKbps", mLinkUpBandwidthKbps);
+        return bundle;
     }
 
-    private static boolean getIsOpportunistic(Phone phone) {
-        SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
-                .getSubscriptionInfoInternal(phone.getSubId());
-        return subInfo != null && subInfo.isOpportunistic();
+    private void log(@NonNull String s) {
+        Rlog.i(mTag, s);
+    }
+
+    private void logd(@NonNull String s) {
+        Rlog.d(mTag, s);
+    }
+
+    private void loge(@NonNull String s) {
+        Rlog.e(mTag, s);
+    }
+
+    private void loge(@NonNull String s, Throwable tr) {
+        Rlog.e(mTag, s, tr);
+    }
+
+    @Override
+    public String toString() {
+        return "DataStallRecoveryStats {"
+            + "Phone id:"
+            + mPhoneId
+            + ", Signal strength:"
+            + mSignalStrength
+            + ", Band:" + mBand
+            + ", RAT:" + mRat
+            + ", Opportunistic:"
+            + mIsOpportunistic
+            + ", Multi-SIM:"
+            + mIsMultiSim
+            + ", Network reg state:"
+            + mNetworkRegState
+            + ", Other signal strength:"
+            + mOtherSignalStrength
+            + ", Other network reg state:"
+            + mOtherNetworkRegState
+            + ", Link status:"
+            + mInternetLinkStatus
+            + ", Link down bandwidth:"
+            + mLinkDownBandwidthKbps
+            + ", Link up bandwidth:"
+            + mLinkUpBandwidthKbps
+            + "}";
     }
 }
diff --git a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
index 5e00987..3e49139 100644
--- a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
+++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
@@ -923,7 +923,9 @@
                 roundAndConvertMillisToSeconds(state.totalTimeMillis),
                 state.isEmergencyOnly,
                 state.isInternetPdnUp,
-                state.foldState);
+                state.foldState,
+                state.overrideVoiceService,
+                state.isDataEnabled);
     }
 
     private static StatsEvent buildStatsEvent(VoiceCallRatUsage usage) {
@@ -975,7 +977,9 @@
                 session.isMultiparty,
                 session.callDuration,
                 session.lastKnownRat,
-                session.foldState);
+                session.foldState,
+                session.ratSwitchCountAfterConnected,
+                session.handoverInProgress);
     }
 
     private static StatsEvent buildStatsEvent(IncomingSms sms) {
diff --git a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
index 5a21baf..d495ca2 100644
--- a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
+++ b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
@@ -1709,7 +1709,9 @@
                     && state.carrierId == key.carrierId
                     && state.isEmergencyOnly == key.isEmergencyOnly
                     && state.isInternetPdnUp == key.isInternetPdnUp
-                    && state.foldState == key.foldState) {
+                    && state.foldState == key.foldState
+                    && state.overrideVoiceService == key.overrideVoiceService
+                    && state.isDataEnabled == key.isDataEnabled) {
                 return state;
             }
         }
diff --git a/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java b/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java
index b830cd0..b6563dd 100644
--- a/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java
+++ b/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java
@@ -15,6 +15,7 @@
  */
 
 package com.android.internal.telephony.metrics;
+
 import static android.telephony.TelephonyManager.DATA_CONNECTED;
 
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
@@ -29,6 +30,7 @@
 import android.telephony.Annotation.NetworkType;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
+import android.telephony.ServiceState.RoamingType;
 import android.telephony.TelephonyManager;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -44,6 +46,7 @@
 import com.android.telephony.Rlog;
 
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
 /** Tracks service state duration and switch metrics for each phone. */
@@ -52,6 +55,7 @@
 
     private final AtomicReference<TimestampedServiceState> mLastState =
             new AtomicReference<>(new TimestampedServiceState(null, 0L));
+    private final AtomicBoolean mOverrideVoiceService = new AtomicBoolean(false);
     private final Phone mPhone;
     private final PersistAtomsStorage mStorage;
     private final DeviceStateHelper mDeviceStateHelper;
@@ -114,8 +118,10 @@
             CellularServiceState newState = new CellularServiceState();
             newState.voiceRat = getVoiceRat(mPhone, serviceState);
             newState.dataRat = getRat(serviceState, NetworkRegistrationInfo.DOMAIN_PS);
-            newState.voiceRoamingType = serviceState.getVoiceRoamingType();
-            newState.dataRoamingType = serviceState.getDataRoamingType();
+            newState.voiceRoamingType =
+                    getNetworkRoamingState(serviceState, NetworkRegistrationInfo.DOMAIN_CS);
+            newState.dataRoamingType =
+                    getNetworkRoamingState(serviceState, NetworkRegistrationInfo.DOMAIN_PS);
             newState.isEndc = isEndc(serviceState);
             newState.simSlotIndex = mPhone.getPhoneId();
             newState.isMultiSim = SimSlotState.isMultiSim();
@@ -123,6 +129,8 @@
             newState.isEmergencyOnly = isEmergencyOnly(serviceState);
             newState.isInternetPdnUp = isInternetPdnUp(mPhone);
             newState.foldState = mDeviceStateHelper.getFoldState();
+            newState.overrideVoiceService = mOverrideVoiceService.get();
+            newState.isDataEnabled = mPhone.getDataSettingsManager().isDataEnabled();
             TimestampedServiceState prevState =
                     mLastState.getAndSet(new TimestampedServiceState(newState, now));
             addServiceStateAndSwitch(
@@ -150,6 +158,26 @@
         }
     }
 
+    /** Updates override state for voice service state when voice calling capability changes */
+    public void onVoiceServiceStateOverrideChanged(boolean override) {
+        if (override == mOverrideVoiceService.get()) {
+            return;
+        }
+        mOverrideVoiceService.set(override);
+        final long now = getTimeMillis();
+        TimestampedServiceState lastState =
+                mLastState.getAndUpdate(
+                        state -> {
+                            if (state.mServiceState == null) {
+                                return new TimestampedServiceState(null, now);
+                            }
+                            CellularServiceState newServiceState = copyOf(state.mServiceState);
+                            newServiceState.overrideVoiceService = mOverrideVoiceService.get();
+                            return new TimestampedServiceState(newServiceState, now);
+                        });
+        addServiceState(lastState, now);
+    }
+
     private void addServiceState(TimestampedServiceState prevState, long now) {
         addServiceStateAndSwitch(prevState, now, null);
     }
@@ -271,6 +299,8 @@
         copy.isEmergencyOnly = state.isEmergencyOnly;
         copy.isInternetPdnUp = state.isInternetPdnUp;
         copy.foldState = state.foldState;
+        copy.overrideVoiceService = state.overrideVoiceService;
+        copy.isDataEnabled = state.isDataEnabled;
         return copy;
     }
 
@@ -380,6 +410,39 @@
         addServiceState(lastState, now);
     }
 
+    private static @RoamingType int getNetworkRoamingState(
+            ServiceState ss, @NetworkRegistrationInfo.Domain int domain) {
+        final NetworkRegistrationInfo nri =
+                ss.getNetworkRegistrationInfo(domain, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        if (nri == null) {
+            // No registration for domain
+            return ServiceState.ROAMING_TYPE_NOT_ROAMING;
+        }
+        @RoamingType int roamingType = nri.getRoamingType();
+        if (nri.isNetworkRoaming() && roamingType == ServiceState.ROAMING_TYPE_NOT_ROAMING) {
+            // Roaming is overridden, exact roaming type unknown.
+            return ServiceState.ROAMING_TYPE_UNKNOWN;
+        }
+        return roamingType;
+    }
+
+    /** Determines whether device is roaming, bypassing carrier overrides. */
+    public static boolean isNetworkRoaming(
+            ServiceState ss, @NetworkRegistrationInfo.Domain int domain) {
+        if (ss == null) {
+            return false;
+        }
+        final NetworkRegistrationInfo nri =
+                ss.getNetworkRegistrationInfo(domain, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        return nri != null && nri.isNetworkRoaming();
+    }
+
+    /** Determines whether device is roaming in any domain, bypassing carrier overrides. */
+    public static boolean isNetworkRoaming(ServiceState ss) {
+        return isNetworkRoaming(ss, NetworkRegistrationInfo.DOMAIN_CS)
+                || isNetworkRoaming(ss, NetworkRegistrationInfo.DOMAIN_PS);
+    }
+
     @VisibleForTesting
     protected long getTimeMillis() {
         return SystemClock.elapsedRealtime();
diff --git a/src/java/com/android/internal/telephony/metrics/SmsStats.java b/src/java/com/android/internal/telephony/metrics/SmsStats.java
index 2f1e6a7..949b72e 100644
--- a/src/java/com/android/internal/telephony/metrics/SmsStats.java
+++ b/src/java/com/android/internal/telephony/metrics/SmsStats.java
@@ -386,7 +386,7 @@
 
     private boolean getIsRoaming() {
         ServiceState serviceState = getServiceState();
-        return serviceState != null ? serviceState.getRoaming() : false;
+        return ServiceStateStats.isNetworkRoaming(serviceState);
     }
 
     private int getCarrierId() {
diff --git a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
index ba07fa0..5d8e027 100644
--- a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
+++ b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
@@ -47,8 +47,10 @@
 import android.telephony.AnomalyReporter;
 import android.telephony.DisconnectCause;
 import android.telephony.NetworkRegistrationInfo;
+import android.telephony.PreciseDataConnectionState;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsStreamMediaProfile;
 import android.util.LongSparseArray;
@@ -163,6 +165,8 @@
     public VoiceCallSessionStats(int phoneId, Phone phone) {
         mPhoneId = phoneId;
         mPhone = phone;
+
+        DataConnectionStateTracker.getInstance(phoneId).start(phone);
     }
 
     /* CS calls */
@@ -395,6 +399,14 @@
         }
     }
 
+    /** Updates internal states when IMS/Emergency PDN/PDU state changes */
+    public synchronized void onPreciseDataConnectionStateChanged(
+            PreciseDataConnectionState connectionState) {
+        if (hasCalls()) {
+            updateVoiceCallSessionBearerState(connectionState);
+        }
+    }
+
     /* internal */
 
     /** Handles ringing MT call getting accepted. */
@@ -426,6 +438,7 @@
         int bearer = getBearer(conn);
         ServiceState serviceState = getServiceState();
         @NetworkType int rat = getVoiceRatWithVoNRFix(mPhone, serviceState, bearer);
+        @VideoState int videoState = conn.getVideoState();
         VoiceCallSession proto = new VoiceCallSession();
 
         proto.bearerAtStart = bearer;
@@ -439,6 +452,7 @@
         proto.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN;
         proto.ratAtEnd = rat;
         proto.ratSwitchCount = 0L;
+        proto.ratSwitchCountAfterConnected = 0L;
         proto.codecBitmask = 0L;
         proto.simSlotIndex = mPhoneId;
         proto.isMultiSim = SimSlotState.isMultiSim();
@@ -449,9 +463,11 @@
         proto.srvccCancellationCount = 0L;
         proto.rttEnabled = false;
         proto.isEmergency = conn.isEmergencyCall() || conn.isNetworkIdentifiedEmergencyCall();
-        proto.isRoaming = serviceState != null ? serviceState.getVoiceRoaming() : false;
+        proto.isRoaming = ServiceStateStats.isNetworkRoaming(serviceState);
         proto.isMultiparty = conn.isMultiparty();
         proto.lastKnownRat = rat;
+        proto.videoEnabled = videoState != VideoProfile.STATE_AUDIO_ONLY ? true : false;
+        proto.handoverInProgress = isHandoverInProgress(bearer, proto.isEmergency);
 
         // internal fields for tracking
         if (getDirection(conn) == VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT) {
@@ -607,6 +623,9 @@
     private void updateRatAtEnd(VoiceCallSession proto, @NetworkType int rat) {
         if (proto.ratAtEnd != rat) {
             proto.ratSwitchCount++;
+            if (!proto.setupFailed) {
+                proto.ratSwitchCountAfterConnected++;
+            }
             proto.ratAtEnd = rat;
             if (rat != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
                 proto.lastKnownRat = rat;
@@ -680,6 +699,17 @@
         return mPhone.getSignalStrength().getLevel();
     }
 
+    private boolean isHandoverInProgress(int bearer, boolean isEmergency) {
+        // If the call is not IMS, the bearer will not be able to handover
+        if (bearer != VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS) {
+            return false;
+        }
+
+        int apnType = isEmergency ? ApnSetting.TYPE_EMERGENCY : ApnSetting.TYPE_IMS;
+        int dataState = DataConnectionStateTracker.getInstance(mPhoneId).getDataState(apnType);
+        return dataState == TelephonyManager.DATA_HANDOVER_IN_PROGRESS;
+    }
+
     /**
      * This is a copy of ServiceStateStats.getVoiceRat(Phone, ServiceState, int) with minimum fix
      * required for tracking EPSFB correctly.
@@ -922,4 +952,40 @@
 
         return map;
     }
+
+    private void updateVoiceCallSessionBearerState(PreciseDataConnectionState connectionState) {
+        ApnSetting apnSetting = connectionState.getApnSetting();
+        if (apnSetting == null) {
+            return;
+        }
+
+        int apnTypes = apnSetting.getApnTypeBitmask();
+        if ((apnTypes & ApnSetting.TYPE_IMS) == 0
+                && (apnTypes & ApnSetting.TYPE_EMERGENCY) == 0) {
+            return;
+        }
+
+        for (int i = 0; i < mCallProtos.size(); i++) {
+            VoiceCallSession proto = mCallProtos.valueAt(i);
+            if (proto.bearerAtEnd == VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS) {
+                if (!proto.isEmergency && (apnTypes & ApnSetting.TYPE_IMS) != 0) {
+                    updateHandoverState(proto, connectionState.getState());
+                }
+                if (proto.isEmergency && (apnTypes & ApnSetting.TYPE_EMERGENCY) != 0) {
+                    updateHandoverState(proto, connectionState.getState());
+                }
+            }
+        }
+    }
+
+    private void updateHandoverState(VoiceCallSession proto, int dataState) {
+        switch (dataState) {
+            case TelephonyManager.DATA_HANDOVER_IN_PROGRESS:
+                proto.handoverInProgress = true;
+                break;
+            default:
+                // All other states are considered as not in handover
+                proto.handoverInProgress = false;
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java b/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java
new file mode 100644
index 0000000..5d4e5dd
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 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.internal.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.Rlog;
+import android.text.TextUtils;
+
+import java.util.List;
+
+/**
+ * This utility class is responsible for resolving NTN capabilities of a
+ * {@link NetworkRegistrationInfo}.
+ */
+public class NtnCapabilityResolver {
+    private static final String TAG = "NtnCapabilityResolver";
+
+    /**
+     * Resolve NTN capability by updating the input NetworkRegistrationInfo to indicate whether
+     * connecting to a non-terrestrial network and the available services supported by the network.
+     *
+     * @param networkRegistrationInfo The NetworkRegistrationInfo of a network.
+     * @param subId The subscription ID associated with a phone.
+     */
+    public static void resolveNtnCapability(
+            @NonNull NetworkRegistrationInfo networkRegistrationInfo, int subId) {
+        SatelliteController satelliteController = SatelliteController.getInstance();
+        List<String> satellitePlmnList = satelliteController.getSatellitePlmnList();
+        String registeredPlmn = networkRegistrationInfo.getRegisteredPlmn();
+        for (String satellitePlmn : satellitePlmnList) {
+            if (TextUtils.equals(satellitePlmn, registeredPlmn)) {
+                logd("Registered to satellite PLMN " + satellitePlmn);
+                networkRegistrationInfo.setIsNonTerrestrialNetwork(true);
+                networkRegistrationInfo.setAvailableServices(
+                        satelliteController.getSupportedSatelliteServices(subId, satellitePlmn));
+                break;
+            }
+        }
+    }
+
+    private static void logd(@NonNull String log) {
+        Rlog.d(TAG, log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/PointingAppController.java b/src/java/com/android/internal/telephony/satellite/PointingAppController.java
index f7f93cf..9dba6f2 100644
--- a/src/java/com/android/internal/telephony/satellite/PointingAppController.java
+++ b/src/java/com/android/internal/telephony/satellite/PointingAppController.java
@@ -37,6 +37,7 @@
 import android.text.TextUtils;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
 
 import java.util.ArrayList;
@@ -92,7 +93,8 @@
      *
      * @param context The Context for the PointingUIController.
      */
-    private PointingAppController(@NonNull Context context) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public PointingAppController(@NonNull Context context) {
         mContext = context;
         mStartedSatelliteTransmissionUpdates = false;
     }
@@ -102,11 +104,21 @@
      * transmission updates
      * @param startedSatelliteTransmissionUpdates boolean to set the flag
      */
+    @VisibleForTesting
     public void setStartedSatelliteTransmissionUpdates(
             boolean startedSatelliteTransmissionUpdates) {
         mStartedSatelliteTransmissionUpdates = startedSatelliteTransmissionUpdates;
     }
 
+    /**
+     * Get the flag mStartedSatelliteTransmissionUpdates
+     * @return returns mStartedSatelliteTransmissionUpdates
+     */
+    @VisibleForTesting
+    public boolean getStartedSatelliteTransmissionUpdates() {
+        return mStartedSatelliteTransmissionUpdates;
+    }
+
     private static final class DatagramTransferStateHandlerRequest {
         public int datagramTransferState;
         public int pendingCount;
@@ -128,6 +140,7 @@
         public static final int EVENT_DATAGRAM_TRANSFER_STATE_CHANGED = 4;
 
         private final ConcurrentHashMap<IBinder, ISatelliteTransmissionUpdateCallback> mListeners;
+
         SatelliteTransmissionUpdateHandler(Looper looper) {
             super(looper);
             mListeners = new ConcurrentHashMap<>();
@@ -265,7 +278,6 @@
                 mSatelliteTransmissionUpdateHandlers.get(subId);
         if (handler != null) {
             handler.removeListener(callback);
-
             if (handler.hasListeners()) {
                 result.accept(SatelliteManager.SATELLITE_ERROR_NONE);
                 return;
@@ -321,9 +333,11 @@
 
     /**
      * Stop receiving satellite transmission updates.
+     * Reset the flag mStartedSatelliteTransmissionUpdates
      * This can be called by the pointing UI when the user stops pointing to the satellite.
      */
     public void stopSatelliteTransmissionUpdates(@NonNull Message message, @Nullable Phone phone) {
+        setStartedSatelliteTransmissionUpdates(false);
         if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) {
             SatelliteModemInterface.getInstance().stopSendingSatellitePointingInfo(message);
             return;
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index 5cd8444..957b152 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony.satellite;
 
+import android.annotation.ArrayRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.bluetooth.BluetoothAdapter;
@@ -25,23 +26,28 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.net.wifi.WifiManager;
 import android.nfc.NfcAdapter;
-import android.nfc.NfcManager;
 import android.os.AsyncResult;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.ICancellationSignal;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.SystemProperties;
 import android.provider.Settings;
+import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -53,8 +59,10 @@
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
 import android.util.Log;
+import android.util.SparseArray;
 import android.uwb.UwbManager;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CommandsInterface;
@@ -63,10 +71,14 @@
 import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
 import com.android.internal.telephony.satellite.metrics.ProvisionMetricsStats;
 import com.android.internal.telephony.satellite.metrics.SessionMetricsStats;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.util.FunctionalUtils;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
@@ -79,6 +91,8 @@
     private static final String TAG = "SatelliteController";
     /** Whether enabling verbose debugging message or not. */
     private static final boolean DBG = false;
+    private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
+    private static final boolean DEBUG = !"user".equals(Build.TYPE);
     /** File used to store shared preferences related to satellite. */
     public static final String SATELLITE_SHARED_PREF = "satellite_shared_pref";
     /** Value to pass for the setting key SATELLITE_MODE_ENABLED, enabled = 1, disabled = 0 */
@@ -153,6 +167,9 @@
     @GuardedBy("mSatelliteEnabledRequestLock")
     private boolean mWaitingForRadioDisabled = false;
 
+    private boolean mWaitingForDisableSatelliteModemResponse = false;
+    private boolean mWaitingForSatelliteModemOff = false;
+
     private final AtomicBoolean mRegisteredForProvisionStateChangedWithSatelliteService =
             new AtomicBoolean(false);
     private final AtomicBoolean mRegisteredForProvisionStateChangedWithPhone =
@@ -193,6 +210,22 @@
     private final Object mNeedsSatellitePointingLock = new Object();
     @GuardedBy("mNeedsSatellitePointingLock")
     private boolean mNeedsSatellitePointing = false;
+    /** Key: subId, value: (key: PLMN, value: set of
+     * {@link android.telephony.NetworkRegistrationInfo.ServiceType})
+     */
+    @GuardedBy("mSupportedSatelliteServicesLock")
+    @NonNull private final Map<Integer, Map<String, Set<Integer>>> mSupportedSatelliteServices =
+            new HashMap<>();
+    @NonNull private final Object mSupportedSatelliteServicesLock = new Object();
+    /** Key: PLMN, value: set of {@link android.telephony.NetworkRegistrationInfo.ServiceType} */
+    @NonNull private final Map<String, Set<Integer>> mSatelliteServicesSupportedByProviders;
+    @NonNull private final CarrierConfigManager mCarrierConfigManager;
+    @NonNull private final CarrierConfigManager.CarrierConfigChangeListener
+            mCarrierConfigChangeListener;
+    @NonNull private final Object mCarrierConfigArrayLock = new Object();
+    @GuardedBy("mCarrierConfigArrayLock")
+    @NonNull private final SparseArray<PersistableBundle> mCarrierConfigArray = new SparseArray<>();
+    @NonNull private final List<String> mSatellitePlmnList;
 
     /**
      * @return The singleton instance of SatelliteController.
@@ -260,6 +293,7 @@
         registerForPendingDatagramCount();
         registerForSatelliteModemStateChanged();
         mContentResolver = mContext.getContentResolver();
+        mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
 
         try {
             mSharedPreferences = mContext.getSharedPreferences(SATELLITE_SHARED_PREF,
@@ -281,6 +315,16 @@
                     Settings.Global.getUriFor(Settings.Global.SATELLITE_MODE_RADIOS),
                     false, satelliteModeRadiosContentObserver);
         }
+
+        mSatelliteServicesSupportedByProviders = readSupportedSatelliteServicesFromOverlayConfig();
+        mSatellitePlmnList =
+                mSatelliteServicesSupportedByProviders.keySet().stream().toList();
+        updateSupportedSatelliteServicesForActiveSubscriptions();
+        mCarrierConfigChangeListener =
+                (slotIndex, subId, carrierId, specificCarrierId) ->
+                        handleCarrierConfigChanged(slotIndex, subId, carrierId, specificCarrierId);
+        mCarrierConfigManager.registerCarrierConfigChangeListener(
+                        new HandlerExecutor(new Handler(looper)), mCarrierConfigChangeListener);
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@@ -418,44 +462,50 @@
                 case BluetoothAdapter.ACTION_STATE_CHANGED:
                     int btState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
                             BluetoothAdapter.ERROR);
-                    logd("Bluetooth state updated to " + btState);
                     synchronized (mRadioStateLock) {
+                        boolean currentBTStateEnabled = mBTStateEnabled;
                         if (btState == BluetoothAdapter.STATE_OFF) {
                             mBTStateEnabled = false;
                             evaluateToSendSatelliteEnabledSuccess();
                         } else if (btState == BluetoothAdapter.STATE_ON) {
                             mBTStateEnabled = true;
                         }
-                        logd("mBTStateEnabled: " + mBTStateEnabled);
+                        if (currentBTStateEnabled != mBTStateEnabled) {
+                            logd("mBTStateEnabled=" + mBTStateEnabled);
+                        }
                     }
                     break;
 
                 case NfcAdapter.ACTION_ADAPTER_STATE_CHANGED:
                     int nfcState = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE, -1);
-                    logd("Nfc state updated to " + nfcState);
                     synchronized (mRadioStateLock) {
+                        boolean currentNfcStateEnabled = mNfcStateEnabled;
                         if (nfcState == NfcAdapter.STATE_ON) {
                             mNfcStateEnabled = true;
                         } else if (nfcState == NfcAdapter.STATE_OFF) {
                             mNfcStateEnabled = false;
                             evaluateToSendSatelliteEnabledSuccess();
                         }
-                        logd("mNfcStateEnabled: " + mNfcStateEnabled);
+                        if (currentNfcStateEnabled != mNfcStateEnabled) {
+                            logd("mNfcStateEnabled=" + mNfcStateEnabled);
+                        }
                     }
                     break;
 
                 case WifiManager.WIFI_STATE_CHANGED_ACTION:
                     int wifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
                             WifiManager.WIFI_STATE_UNKNOWN);
-                    logd("Wifi state updated to " + wifiState);
                     synchronized (mRadioStateLock) {
+                        boolean currentWifiStateEnabled = mWifiStateEnabled;
                         if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
                             mWifiStateEnabled = true;
                         } else if (wifiState == WifiManager.WIFI_STATE_DISABLED) {
                             mWifiStateEnabled = false;
                             evaluateToSendSatelliteEnabledSuccess();
                         }
-                        logd("mWifiStateEnabled: " + mWifiStateEnabled);
+                        if (currentWifiStateEnabled != mWifiStateEnabled) {
+                            logd("mWifiStateEnabled=" + mWifiStateEnabled);
+                        }
                     }
                     break;
                 default:
@@ -686,17 +736,17 @@
                                         SatelliteManager.SATELLITE_ERROR_NONE);
                             }
                         }
-                        resetSatelliteEnabledRequest();
 
-                        setSettingsKeyForSatelliteMode(SATELLITE_MODE_ENABLED_FALSE);
-                        setDemoModeEnabled(argument.enableDemoMode);
                         synchronized (mIsSatelliteEnabledLock) {
-                            mIsSatelliteEnabled = argument.enableSatellite;
+                            if (!mWaitingForSatelliteModemOff) {
+                                moveSatelliteToOffStateAndCleanUpResources(
+                                        SatelliteManager.SATELLITE_ERROR_NONE, argument.callback);
+                            } else {
+                                logd("Wait for satellite modem off before updating satellite"
+                                        + " modem state");
+                            }
+                            mWaitingForDisableSatelliteModemResponse = false;
                         }
-                        // If satellite is disabled, send success to callback immediately
-                        argument.callback.accept(error);
-                        updateSatelliteEnabledState(
-                                argument.enableSatellite, "EVENT_SET_SATELLITE_ENABLED_DONE");
                     }
                 } else {
                     synchronized (mSatelliteEnabledRequestLock) {
@@ -728,6 +778,9 @@
                             .reportSessionMetrics();
                 } else {
                     mControllerMetricsStats.onSatelliteDisabled();
+                    synchronized (mIsSatelliteEnabledLock) {
+                        mWaitingForDisableSatelliteModemResponse = false;
+                    }
                 }
                 break;
             }
@@ -951,19 +1004,24 @@
                         || mCi.getRadioState() == TelephonyManager.RADIO_POWER_UNAVAILABLE) {
                     mIsRadioOn = false;
                     logd("Radio State Changed to " + mCi.getRadioState());
-                    IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
-                        @Override
-                        public void accept(int result) {
-                            logd("RequestSatelliteEnabled: result=" + result);
-                        }
-                    };
-                    Phone phone = SatelliteServiceUtils.getPhone();
-                    Consumer<Integer> result = FunctionalUtils
-                            .ignoreRemoteException(errorCallback::accept);
-                    RequestSatelliteEnabledArgument message =
-                            new RequestSatelliteEnabledArgument(false, false, result);
-                    request = new SatelliteControllerHandlerRequest(message, phone);
-                    handleSatelliteEnabled(request);
+                    if (isSatelliteEnabled()) {
+                        IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
+                            @Override
+                            public void accept(int result) {
+                                logd("RequestSatelliteEnabled: result=" + result);
+                            }
+                        };
+                        Phone phone = SatelliteServiceUtils.getPhone();
+                        Consumer<Integer> result = FunctionalUtils
+                                .ignoreRemoteException(errorCallback::accept);
+                        RequestSatelliteEnabledArgument message =
+                                new RequestSatelliteEnabledArgument(false, false, result);
+                        request = new SatelliteControllerHandlerRequest(message, phone);
+                        handleSatelliteEnabled(request);
+                    } else {
+                        logd("EVENT_RADIO_STATE_CHANGED: Satellite modem is currently disabled."
+                                + " Ignored the event");
+                    }
                 } else {
                     mIsRadioOn = true;
                     if (!mSatelliteModemInterface.isSatelliteServiceSupported()) {
@@ -1155,13 +1213,13 @@
             if (mSatelliteEnabledRequest == null) {
                 mSatelliteEnabledRequest = request;
             } else if (mSatelliteEnabledRequest.enableSatellite == request.enableSatellite) {
-                logd("requestSatelliteEnabled  enableSatellite: " + enableSatellite
+                logd("requestSatelliteEnabled enableSatellite: " + enableSatellite
                         + " is already in progress.");
                 result.accept(SatelliteManager.SATELLITE_REQUEST_IN_PROGRESS);
                 return;
             } else if (mSatelliteEnabledRequest.enableSatellite == false
                     && request.enableSatellite == true) {
-                logd("requestSatelliteEnabled  enableSatellite: " + enableSatellite + " cannot be "
+                logd("requestSatelliteEnabled enableSatellite: " + enableSatellite + " cannot be "
                         + "processed. Disable satellite is already in progress.");
                 result.accept(SatelliteManager.SATELLITE_ERROR);
                 return;
@@ -1763,33 +1821,24 @@
      * {@code false} otherwise.
      */
     public boolean setSatelliteServicePackageName(@Nullable String servicePackageName) {
-        boolean result = mSatelliteModemInterface.setSatelliteServicePackageName(
-                servicePackageName);
-        if (result) {
-            logd("setSatelliteServicePackageName: Resetting cached states");
+        if (!isMockModemAllowed()) return false;
 
-            // Cached states need to be cleared whenever switching satellite vendor services.
-            synchronized (mIsSatelliteSupportedLock) {
-                mIsSatelliteSupported = null;
-            }
-            synchronized (mIsSatelliteProvisionedLock) {
-                mIsSatelliteProvisioned = null;
-            }
-            synchronized (mIsSatelliteEnabledLock) {
-                mIsSatelliteEnabled = null;
-            }
-            synchronized (mSatelliteCapabilitiesLock) {
-                mSatelliteCapabilities = null;
-            }
-            ResultReceiver receiver = new ResultReceiver(this) {
-                @Override
-                protected void onReceiveResult(int resultCode, Bundle resultData) {
-                    logd("requestIsSatelliteSupported: resultCode=" + resultCode);
-                }
-            };
-            requestIsSatelliteSupported(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, receiver);
+        // Cached states need to be cleared whenever switching satellite vendor services.
+        logd("setSatelliteServicePackageName: Resetting cached states");
+        synchronized (mIsSatelliteSupportedLock) {
+            mIsSatelliteSupported = null;
         }
-        return result;
+        synchronized (mIsSatelliteProvisionedLock) {
+            mIsSatelliteProvisioned = null;
+        }
+        synchronized (mIsSatelliteEnabledLock) {
+            mIsSatelliteEnabled = null;
+        }
+        synchronized (mSatelliteCapabilitiesLock) {
+            mSatelliteCapabilities = null;
+        }
+        mSatelliteModemInterface.setSatelliteServicePackageName(servicePackageName);
+        return true;
     }
 
     /**
@@ -1891,6 +1940,40 @@
     }
 
     /**
+     * @return The list of satellite PLMNs used for connecting to satellite networks.
+     */
+    @NonNull
+    public List<String> getSatellitePlmnList() {
+        return new ArrayList<>(mSatellitePlmnList);
+    }
+
+    /**
+     * @param subId Subscription ID.
+     * @param plmn The satellite roaming plmn.
+     * @return The list of services supported by the carrier associated with the {@code subId} for
+     * the satellite network {@code plmn}.
+     */
+    @NonNull
+    public List<Integer> getSupportedSatelliteServices(int subId, String plmn) {
+        synchronized (mSupportedSatelliteServicesLock) {
+            if (mSupportedSatelliteServices.containsKey(subId)) {
+                Map<String, Set<Integer>> supportedServices =
+                        mSupportedSatelliteServices.get(subId);
+                if (supportedServices != null && supportedServices.containsKey(plmn)) {
+                    return new ArrayList<>(supportedServices.get(plmn));
+                } else {
+                    loge("getSupportedSatelliteServices: subId=" + subId + ", supportedServices "
+                            + "does not contain key plmn=" + plmn);
+                }
+            } else {
+                loge("getSupportedSatelliteServices: mSupportedSatelliteServices does contain key"
+                        + " subId=" + subId);
+            }
+            return new ArrayList<>();
+        }
+    }
+
+    /**
      * If we have not successfully queried the satellite modem for its satellite service support,
      * we will retry the query one more time. Otherwise, we will return the cached result.
      */
@@ -2045,13 +2128,23 @@
     private void handleSatelliteEnabled(SatelliteControllerHandlerRequest request) {
         RequestSatelliteEnabledArgument argument =
                 (RequestSatelliteEnabledArgument) request.argument;
+        Phone phone = request.phone;
+
+        if (!argument.enableSatellite && (mSatelliteModemInterface.isSatelliteServiceSupported()
+                || phone != null)) {
+            synchronized (mIsSatelliteEnabledLock) {
+                mWaitingForDisableSatelliteModemResponse = true;
+                mWaitingForSatelliteModemOff = true;
+            }
+        }
+
         Message onCompleted = obtainMessage(EVENT_SET_SATELLITE_ENABLED_DONE, request);
         if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
             mSatelliteModemInterface.requestSatelliteEnabled(argument.enableSatellite,
                     argument.enableDemoMode, onCompleted);
             return;
         }
-        Phone phone = request.phone;
+
         if (phone != null) {
             phone.setSatellitePower(onCompleted, argument.enableSatellite);
         } else {
@@ -2190,15 +2283,31 @@
     private void handleEventSatelliteModemStateChanged(
             @SatelliteManager.SatelliteModemState int state) {
         logd("handleEventSatelliteModemStateChanged: state=" + state);
-        if (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF
-                || state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE) {
-            setSettingsKeyForSatelliteMode(SATELLITE_MODE_ENABLED_FALSE);
-            setDemoModeEnabled(false);
-            updateSatelliteEnabledState(
-                    false, "handleEventSatelliteModemStateChanged");
-            cleanUpResources(state);
+        if (state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE
+                || state == SatelliteManager.SATELLITE_MODEM_STATE_OFF) {
+            synchronized (mIsSatelliteEnabledLock) {
+                if ((state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE)
+                        || ((mIsSatelliteEnabled == null || isSatelliteEnabled())
+                        && !mWaitingForDisableSatelliteModemResponse)) {
+                    int error = (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF)
+                            ? SatelliteManager.SATELLITE_ERROR_NONE
+                            : SatelliteManager.SATELLITE_INVALID_MODEM_STATE;
+                    Consumer<Integer> callback = null;
+                    synchronized (mSatelliteEnabledRequestLock) {
+                        if (mSatelliteEnabledRequest != null) {
+                            callback = mSatelliteEnabledRequest.callback;
+                        }
+                    }
+                    moveSatelliteToOffStateAndCleanUpResources(error, callback);
+                } else {
+                    logd("Either waiting for the response of disabling satellite modem or the event"
+                            + " should be ignored because isSatelliteEnabled="
+                            + isSatelliteEnabled()
+                            + ", mIsSatelliteEnabled=" + mIsSatelliteEnabled);
+                }
+                mWaitingForSatelliteModemOff = false;
+            }
         }
-
         mDatagramController.onSatelliteModemStateChanged(state);
     }
 
@@ -2252,16 +2361,17 @@
         }
     }
 
-    private void cleanUpResources(@SatelliteManager.SatelliteModemState int state) {
-        logd("cleanUpResources");
-        if (state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE) {
-            synchronized (mSatelliteEnabledRequestLock) {
-                if (mSatelliteEnabledRequest != null) {
-                    mSatelliteEnabledRequest.callback.accept(
-                            SatelliteManager.SATELLITE_INVALID_MODEM_STATE);
-                }
-            }
+    private void moveSatelliteToOffStateAndCleanUpResources(
+            @SatelliteManager.SatelliteError int error, @Nullable Consumer<Integer> callback) {
+        logd("moveSatelliteToOffStateAndCleanUpResources");
+        synchronized (mIsSatelliteEnabledLock) {
             resetSatelliteEnabledRequest();
+            setDemoModeEnabled(false);
+            mIsSatelliteEnabled = false;
+            setSettingsKeyForSatelliteMode(SATELLITE_MODE_ENABLED_FALSE);
+            if (callback != null) callback.accept(error);
+            updateSatelliteEnabledState(
+                    false, "moveSatelliteToOffStateAndCleanUpResources");
         }
     }
 
@@ -2270,6 +2380,100 @@
         mDatagramController.setDemoMode(mIsDemoModeEnabled);
     }
 
+    private boolean isMockModemAllowed() {
+        return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false));
+    }
+
+    private void updateSupportedSatelliteServicesForActiveSubscriptions() {
+        synchronized (mSupportedSatelliteServicesLock) {
+            mSupportedSatelliteServices.clear();
+            int[] activeSubIds = SubscriptionManagerService.getInstance().getActiveSubIdList(true);
+            if (activeSubIds != null) {
+                for (int subId : activeSubIds) {
+                    updateSupportedSatelliteServices(subId);
+                }
+            } else {
+                loge("updateSupportedSatelliteServicesForActiveSubscriptions: "
+                        + "activeSubIds is null");
+            }
+        }
+    }
+
+    private void updateSupportedSatelliteServices(int subId) {
+        Map<String, Set<Integer>> carrierSupportedSatelliteServicesPerPlmn =
+                readSupportedSatelliteServicesFromCarrierConfig(subId);
+        synchronized (mSupportedSatelliteServicesLock) {
+            mSupportedSatelliteServices.put(subId,
+                    SatelliteServiceUtils.mergeSupportedSatelliteServices(
+                            mSatelliteServicesSupportedByProviders,
+                            carrierSupportedSatelliteServicesPerPlmn));
+        }
+    }
+
+    @NonNull
+    private Map<String, Set<Integer>> readSupportedSatelliteServicesFromOverlayConfig() {
+        String[] supportedServices = readStringArrayFromOverlayConfig(
+                R.array.config_satellite_services_supported_by_providers);
+        return SatelliteServiceUtils.parseSupportedSatelliteServices(supportedServices);
+    }
+
+    @NonNull
+    private Map<String, Set<Integer>> readSupportedSatelliteServicesFromCarrierConfig(int subId) {
+        synchronized (mCarrierConfigArrayLock) {
+            PersistableBundle config = mCarrierConfigArray.get(subId);
+            if (config == null) {
+                config = getConfigForSubId(subId);
+                mCarrierConfigArray.put(subId, config);
+            }
+            return SatelliteServiceUtils.parseSupportedSatelliteServices(
+                    config.getPersistableBundle(CarrierConfigManager
+                            .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE));
+        }
+    }
+
+    @NonNull private PersistableBundle getConfigForSubId(int subId) {
+        PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId,
+                CarrierConfigManager
+                        .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE);
+        if (config == null || config.isEmpty()) {
+            config = CarrierConfigManager.getDefaultConfig();
+        }
+        return config;
+    }
+
+    private void handleCarrierConfigChanged(int slotIndex, int subId, int carrierId,
+            int specificCarrierId) {
+        logd("handleCarrierConfigChanged(): slotIndex(" + slotIndex + "), subId("
+                + subId + "), carrierId(" + carrierId + "), specificCarrierId("
+                + specificCarrierId + ")");
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return;
+        }
+
+        updateCarrierConfig(subId);
+        updateSupportedSatelliteServicesForActiveSubscriptions();
+    }
+
+    private void updateCarrierConfig(int subId) {
+        synchronized (mCarrierConfigArrayLock) {
+            mCarrierConfigArray.put(subId, getConfigForSubId(subId));
+        }
+    }
+
+    @NonNull
+    private String[] readStringArrayFromOverlayConfig(@ArrayRes int id) {
+        String[] strArray = null;
+        try {
+            strArray = mContext.getResources().getStringArray(id);
+        } catch (Resources.NotFoundException ex) {
+            loge("readStringArrayFromOverlayConfig: id= " + id + ", ex=" + ex);
+        }
+        if (strArray == null) {
+            strArray = new String[0];
+        }
+        return strArray;
+    }
+
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
index 80c67b3..ebf7780 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
@@ -24,14 +24,12 @@
 import android.content.ServiceConnection;
 import android.os.AsyncResult;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RegistrantList;
 import android.os.RemoteException;
-import android.os.SystemProperties;
 import android.telephony.Rlog;
 import android.telephony.satellite.SatelliteCapabilities;
 import android.telephony.satellite.SatelliteDatagram;
@@ -57,8 +55,6 @@
  */
 public class SatelliteModemInterface {
     private static final String TAG = "SatelliteModemInterface";
-    private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
-    private static final boolean DEBUG = !"user".equals(Build.TYPE);
     private static final long REBIND_INITIAL_DELAY = 2 * 1000; // 2 seconds
     private static final long REBIND_MAXIMUM_DELAY = 64 * 1000; // 1 minute
     private static final int REBIND_MULTIPLIER = 2;
@@ -1016,13 +1012,7 @@
      * {@code false} otherwise.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public boolean setSatelliteServicePackageName(@Nullable String servicePackageName) {
-        if (!shouldAllowModifyingSatelliteServicePackageName()) {
-            loge("setSatelliteServicePackageName: modifying satellite service package name "
-                    + "is not allowed");
-            return false;
-        }
-
+    public void setSatelliteServicePackageName(@Nullable String servicePackageName) {
         logd("setSatelliteServicePackageName: config_satellite_service_package is "
                 + "updated, new packageName=" + servicePackageName);
         mExponentialBackoff.stop();
@@ -1042,8 +1032,6 @@
         mIsSatelliteServiceSupported = getSatelliteServiceSupport();
         bindService();
         mExponentialBackoff.start();
-
-        return true;
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@@ -1055,10 +1043,6 @@
         message.sendToTarget();
     }
 
-    private boolean shouldAllowModifyingSatelliteServicePackageName() {
-        return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false));
-    }
-
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java b/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
index f11ca66..151b69d 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
@@ -16,11 +16,16 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.telephony.NetworkRegistrationInfo.FIRST_SERVICE_TYPE;
+import static android.telephony.NetworkRegistrationInfo.LAST_SERVICE_TYPE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.os.AsyncResult;
 import android.os.Binder;
+import android.os.PersistableBundle;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
 import android.telephony.satellite.AntennaPosition;
@@ -40,7 +45,9 @@
 
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -267,6 +274,127 @@
     }
 
     /**
+     * Expected format of each input string in the array: "PLMN_1:service_1,service_2,..."
+     *
+     * @return The map of supported services with key: PLMN, value: set of services supported by
+     * the PLMN.
+     */
+    @NonNull
+    @NetworkRegistrationInfo.ServiceType
+    public static Map<String, Set<Integer>> parseSupportedSatelliteServices(
+            String[] supportedSatelliteServicesStrArray) {
+        Map<String, Set<Integer>> supportedServicesMap = new HashMap<>();
+        if (supportedSatelliteServicesStrArray == null
+                || supportedSatelliteServicesStrArray.length == 0) {
+            return supportedServicesMap;
+        }
+
+        for (String supportedServicesPerPlmnStr : supportedSatelliteServicesStrArray) {
+            String[] pairOfPlmnAndsupportedServicesStr =
+                    supportedServicesPerPlmnStr.split(":");
+            if (pairOfPlmnAndsupportedServicesStr != null
+                    && (pairOfPlmnAndsupportedServicesStr.length == 1
+                    || pairOfPlmnAndsupportedServicesStr.length == 2)) {
+                String plmn = pairOfPlmnAndsupportedServicesStr[0];
+                Set<Integer> supportedServicesSet = new HashSet<>();
+                if (pairOfPlmnAndsupportedServicesStr.length == 2) {
+                    String[] supportedServicesStrArray =
+                            pairOfPlmnAndsupportedServicesStr[1].split(",");
+                    for (String service : supportedServicesStrArray) {
+                        try {
+                            int serviceType = Integer.parseInt(service);
+                            if (isServiceTypeValid(serviceType)) {
+                                supportedServicesSet.add(serviceType);
+                            } else {
+                                loge("parseSupportedSatelliteServices: invalid serviceType="
+                                        + serviceType);
+                            }
+                        } catch (NumberFormatException e) {
+                            loge("parseSupportedSatelliteServices: supportedServicesPerPlmnStr="
+                                    + supportedServicesPerPlmnStr + ", service=" + service
+                                    + ", e=" + e);
+                        }
+                    }
+                }
+                supportedServicesMap.put(plmn, supportedServicesSet);
+            } else {
+                loge("parseSupportedSatelliteServices: invalid format input, "
+                        + "supportedServicesPerPlmnStr=" + supportedServicesPerPlmnStr);
+            }
+        }
+        return supportedServicesMap;
+    }
+
+    /**
+     * Expected format of the input dictionary bundle is:
+     * <ul>
+     *     <li>Key: PLMN string.</li>
+     *     <li>Value: A string with format "service_1,service_2,..."</li>
+     * </ul>
+     * @return The map of supported services with key: PLMN, value: set of services supported by
+     * the PLMN.
+     */
+    @NonNull
+    @NetworkRegistrationInfo.ServiceType
+    public static Map<String, Set<Integer>> parseSupportedSatelliteServices(
+            PersistableBundle supportedServicesBundle) {
+        Map<String, Set<Integer>> supportedServicesMap = new HashMap<>();
+        if (supportedServicesBundle == null || supportedServicesBundle.isEmpty()) {
+            return supportedServicesMap;
+        }
+
+        for (String plmn : supportedServicesBundle.keySet()) {
+            Set<Integer> supportedServicesSet = new HashSet<>();
+            for (int serviceType : supportedServicesBundle.getIntArray(plmn)) {
+                if (isServiceTypeValid(serviceType)) {
+                    supportedServicesSet.add(serviceType);
+                } else {
+                    loge("parseSupportedSatelliteServices: invalid service type=" + serviceType
+                            + " for plmn=" + plmn);
+                }
+            }
+            supportedServicesMap.put(plmn, supportedServicesSet);
+        }
+        return supportedServicesMap;
+    }
+
+    /**
+     * For the PLMN that exists in both {@code providerSupportedServices} and
+     * {@code carrierSupportedServices}, the supported services will be the intersection of the two
+     * sets. For the PLMN that is present in {@code providerSupportedServices} but not in
+     * {@code carrierSupportedServices}, the provider supported services will be used. The rest
+     * will not be used.
+     *
+     * @param providerSupportedServices Satellite provider supported satellite services.
+     * @param carrierSupportedServices Carrier supported satellite services.
+     * @return The supported satellite services by the device for the corresponding carrier and the
+     * satellite provider.
+     */
+    @NonNull
+    @NetworkRegistrationInfo.ServiceType
+    public static Map<String, Set<Integer>> mergeSupportedSatelliteServices(
+            @NonNull @NetworkRegistrationInfo.ServiceType Map<String, Set<Integer>>
+                    providerSupportedServices,
+            @NonNull @NetworkRegistrationInfo.ServiceType Map<String, Set<Integer>>
+                    carrierSupportedServices) {
+        Map<String, Set<Integer>> supportedServicesMap = new HashMap<>();
+        for (Map.Entry<String, Set<Integer>> entry : providerSupportedServices.entrySet()) {
+            Set<Integer> supportedServices = new HashSet<>(entry.getValue());
+            if (carrierSupportedServices.containsKey(entry.getKey())) {
+                supportedServices.retainAll(carrierSupportedServices.get(entry.getKey()));
+            }
+            if (!supportedServices.isEmpty()) {
+                supportedServicesMap.put(entry.getKey(), supportedServices);
+            }
+        }
+        return supportedServicesMap;
+    }
+
+    private static boolean isServiceTypeValid(int serviceType) {
+        return (serviceType >= FIRST_SERVICE_TYPE && serviceType <= LAST_SERVICE_TYPE);
+    }
+
+    /**
      * Return phone associated with phoneId 0.
      *
      * @return phone associated with phoneId 0 or {@code null} if it doesn't exist.
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
index b90dc5e..124437c 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
@@ -2018,21 +2018,21 @@
     }
 
     /**
-     * Reload the database from content provider to the cache.
+     * Reload the database from content provider to the cache. This must be a synchronous operation
+     * to prevent cache/database out-of-sync. Callers should be cautious to call this method because
+     * it might take longer time to complete.
      */
-    public void reloadDatabase() {
-        if (mAsyncMode) {
-            post(this::loadDatabaseInternal);
-        } else {
-            loadDatabaseInternal();
-        }
+    public void reloadDatabaseSync() {
+        logl("reloadDatabaseSync");
+        // Synchronously load the database into the cache.
+        loadDatabaseInternal();
     }
 
     /**
      * Load the database from content provider to the cache.
      */
     private void loadDatabaseInternal() {
-        log("loadDatabaseInternal");
+        logl("loadDatabaseInternal");
         try (Cursor cursor = mContext.getContentResolver().query(
                 SimInfo.CONTENT_URI, null, null, null, null)) {
             mReadWriteLock.writeLock().lock();
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
index a3377a2..ba69d8a 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
@@ -1069,6 +1069,8 @@
                         int subId = insertSubscriptionInfo(embeddedProfile.getIccid(),
                                 SubscriptionManager.INVALID_SIM_SLOT_INDEX,
                                 null, SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
+                        mSubscriptionDatabaseManager.setDisplayName(subId, mContext.getResources()
+                                .getString(R.string.default_card_name, subId));
                         subInfo = mSubscriptionDatabaseManager.getSubscriptionInfoInternal(subId);
                     }
 
@@ -1116,6 +1118,7 @@
                     // CARD_ID field should not contain the EID
                     if (cardId >= 0 && mUiccController.getCardIdForDefaultEuicc()
                             != TelephonyManager.UNSUPPORTED_CARD_ID) {
+                        builder.setCardId(cardId);
                         builder.setCardString(mUiccController.convertToCardString(cardId));
                     }
 
@@ -1345,6 +1348,8 @@
                     // This is a new SIM card. Insert a new record.
                     subId = insertSubscriptionInfo(iccId, phoneId, null,
                             SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
+                    mSubscriptionDatabaseManager.setDisplayName(subId,
+                            mContext.getResources().getString(R.string.default_card_name, subId));
                 } else {
                     subId = subInfo.getSubscriptionId();
                     log("updateSubscription: Found existing subscription. subId= " + subId
@@ -1421,12 +1426,15 @@
                     }
 
                     // Attempt to restore SIM specific settings when SIM is loaded.
-                    mContext.getContentResolver().call(
+                    Bundle result = mContext.getContentResolver().call(
                             SubscriptionManager.SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI,
                             SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME,
                             iccId, null);
-                    log("Reload the database.");
-                    mSubscriptionDatabaseManager.reloadDatabase();
+                    if (result != null && result.getBoolean(
+                            SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_DATABASE_UPDATED)) {
+                        logl("Sim specific settings changed the database.");
+                        mSubscriptionDatabaseManager.reloadDatabaseSync();
+                    }
                 }
 
                 log("updateSubscription: " + mSubscriptionDatabaseManager
@@ -3562,10 +3570,10 @@
      *
      * @param subId the unique SubscriptionInfo index in database
      * @return userHandle associated with this subscription
-     * or {@code null} if subscription is not associated with any user.
+     * or {@code null} if subscription is not associated with any user
+     * or {code null} if subscripiton is not available on the device.
      *
      * @throws SecurityException if doesn't have required permission.
-     * @throws IllegalArgumentException if {@code subId} is invalid.
      */
     @Override
     @Nullable
@@ -3578,8 +3586,7 @@
             SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
                     .getSubscriptionInfoInternal(subId);
             if (subInfo == null) {
-                throw new IllegalArgumentException("getSubscriptionUserHandle: Invalid subId: "
-                        + subId);
+                return null;
             }
 
             UserHandle userHandle = UserHandle.of(subInfo.getUserId());
@@ -3727,12 +3734,16 @@
             Bundle bundle = new Bundle();
             bundle.putByteArray(SubscriptionManager.KEY_SIM_SPECIFIC_SETTINGS_DATA, data);
             logl("restoreAllSimSpecificSettingsFromBackup");
-            mContext.getContentResolver().call(
+            Bundle result = mContext.getContentResolver().call(
                     SubscriptionManager.SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI,
                     SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME,
                     null, bundle);
-            // After restoring, we need to reload the content provider into the cache.
-            mSubscriptionDatabaseManager.reloadDatabase();
+
+            if (result != null && result.getBoolean(
+                    SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_DATABASE_UPDATED)) {
+                logl("Sim specific settings changed the database.");
+                mSubscriptionDatabaseManager.reloadDatabaseSync();
+            }
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java b/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java
index be045c4..680af46 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java
@@ -106,14 +106,21 @@
                 mCallback.sendToTarget();
                 return;
             }
-
+            IccIoResult response;
             switch (msg.what) {
                 case EVENT_SELECT_FILE_DONE:
-                    readBinary();
+                    response = (IccIoResult) ar.result;
+                    if (response.getException() == null) {
+                        readBinary();
+                    } else {
+                        log("Select file error : " + response.getException());
+                        AsyncResult.forMessage(mCallback, null, response.getException());
+                        mCallback.sendToTarget();
+                    }
                     break;
 
                 case EVENT_READ_BINARY_DONE:
-                    IccIoResult response = (IccIoResult) ar.result;
+                    response = (IccIoResult) ar.result;
                     String result = IccUtils.bytesToHexString(response.payload)
                             .toUpperCase(Locale.US);
                     log("IccIoResult: " + response + " payload: " + result);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java
index fadbff1..dfb91a5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java
@@ -16,12 +16,15 @@
 
 package com.android.internal.telephony;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.isA;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -97,6 +100,7 @@
     private void setDefaultValues() {
         mBundle.putInt(CarrierConfigManager.KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT, 0);
         mBundle.putInt(CarrierConfigManager.KEY_EMERGENCY_NOTIFICATION_DELAY_INT, 0);
+        mBundle.putBoolean(CarrierConfigManager.KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL, false);
     }
 
     @After
@@ -232,4 +236,39 @@
         verify(mNotificationManager, atLeast(2)).cancel(
                 CarrierServiceStateTracker.EMERGENCY_NOTIFICATION_TAG, SUB_ID);
     }
+
+    @Test
+    public void testSetEnabledNotifications() {
+        logd(LOG_TAG + ":testSetEnabledNotifications()");
+
+        mBundle.putBoolean(CarrierConfigManager.KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL, true);
+
+        Notification.Builder mNotificationBuilder = new Notification.Builder(mContext);
+        doReturn(mNotificationBuilder).when(mSpyCarrierSST).getNotificationBuilder(any());
+        doReturn(mNotificationManager).when(mSpyCarrierSST).getNotificationManager(any());
+        doReturn(true).when(mPhone).isWifiCallingEnabled(); // notifiable for emergency
+        mCarrierConfigChangeListener.onCarrierConfigChanged(0 /* slotIndex */, SUB_ID,
+                TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+        processAllMessages();
+
+        Map<Integer, CarrierServiceStateTracker.NotificationType> notificationTypeMap =
+                mCarrierSST.getNotificationTypeMap();
+        CarrierServiceStateTracker.NotificationType prefNetworkNotification =
+                notificationTypeMap.get(CarrierServiceStateTracker.NOTIFICATION_PREF_NETWORK);
+        CarrierServiceStateTracker.NotificationType emergencyNetworkNotification =
+                notificationTypeMap.get(CarrierServiceStateTracker.NOTIFICATION_EMERGENCY_NETWORK);
+        assertFalse(prefNetworkNotification.isEnabled());
+        assertTrue(emergencyNetworkNotification.isEnabled());
+
+        verify(mNotificationManager, never()).notify(
+                eq(CarrierServiceStateTracker.PREF_NETWORK_NOTIFICATION_TAG),
+                eq(SUB_ID), isA(Notification.class));
+        verify(mNotificationManager, atLeast(1)).cancel(
+                CarrierServiceStateTracker.PREF_NETWORK_NOTIFICATION_TAG, SUB_ID);
+        verify(mNotificationManager, atLeast(1)).notify(
+                eq(CarrierServiceStateTracker.EMERGENCY_NOTIFICATION_TAG),
+                eq(SUB_ID), isA(Notification.class));
+        verify(mNotificationManager, never()).cancel(
+                CarrierServiceStateTracker.EMERGENCY_NOTIFICATION_TAG, SUB_ID);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
index c5f20e3..465880a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
@@ -2211,7 +2211,6 @@
         verify(mMockCi, times(1)).setNullCipherAndIntegrityEnabled(anyBoolean(),
                 any(Message.class));
 
-        // Some ephemeral error occurred in the modem, but the feature was supported
         mPhoneUT.sendMessage(mPhoneUT.obtainMessage(EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE,
                 new AsyncResult(null, null,
                         new CommandException(CommandException.Error.REQUEST_NOT_SUPPORTED))));
@@ -2220,6 +2219,28 @@
     }
 
     @Test
+    public void testHandleNullCipherAndIntegrityEnabled_radioUnavailable() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CELLULAR_SECURITY,
+                TelephonyManager.PROPERTY_ENABLE_NULL_CIPHER_TOGGLE, Boolean.TRUE.toString(),
+                false);
+        mPhoneUT.mCi = mMockCi;
+        assertFalse(mPhoneUT.isNullCipherAndIntegritySupported());
+
+        mPhoneUT.sendMessage(mPhoneUT.obtainMessage(EVENT_RADIO_AVAILABLE,
+                new AsyncResult(null, new int[]{ServiceState.RIL_RADIO_TECHNOLOGY_GSM}, null)));
+        processAllMessages();
+
+        verify(mMockCi, times(1)).setNullCipherAndIntegrityEnabled(anyBoolean(),
+                any(Message.class));
+
+        mPhoneUT.sendMessage(mPhoneUT.obtainMessage(EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE,
+                new AsyncResult(null, null,
+                        new CommandException(CommandException.Error.RADIO_NOT_AVAILABLE))));
+        processAllMessages();
+        assertFalse(mPhoneUT.isNullCipherAndIntegritySupported());
+    }
+
+    @Test
     public void testHandleNullCipherAndIntegrityEnabled_radioSupportsFeature() {
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CELLULAR_SECURITY,
                 TelephonyManager.PROPERTY_ENABLE_NULL_CIPHER_TOGGLE, Boolean.TRUE.toString(),
@@ -2234,7 +2255,6 @@
         verify(mMockCi, times(1)).setNullCipherAndIntegrityEnabled(anyBoolean(),
                 any(Message.class));
 
-        // Some ephemeral error occurred in the modem, but the feature was supported
         mPhoneUT.sendMessage(mPhoneUT.obtainMessage(EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE,
                 new AsyncResult(null, null, null)));
         processAllMessages();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java
index 1c44772..fe1404b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java
@@ -342,9 +342,6 @@
     public void testFallbackGsmRetrywithMessageRef() throws Exception {
         int token = mImsSmsDispatcher.mNextToken.get();
         int messageRef = mImsSmsDispatcher.nextMessageRef();
-        if (mImsSmsDispatcher.isMessageRefIncrementViaTelephony()) {
-            messageRef += 1;
-        }
 
         when(mImsManager.getSmsFormat()).thenReturn(SmsMessage.FORMAT_3GPP);
         when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM);
@@ -363,11 +360,7 @@
         ArgumentCaptor<SMSDispatcher.SmsTracker> captor =
                 ArgumentCaptor.forClass(SMSDispatcher.SmsTracker.class);
         verify(mSmsDispatchersController).sendRetrySms(captor.capture());
-        if (mImsSmsDispatcher.isMessageRefIncrementViaTelephony()) {
-            assertTrue(messageRef + 1 == captor.getValue().mMessageRef);
-        } else {
-            assertTrue(messageRef == captor.getValue().mMessageRef);
-        }
+        assertTrue(messageRef == captor.getValue().mMessageRef);
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
index 19be558..03b1cfd 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
@@ -319,4 +319,19 @@
         mLocaleTracker.updateOperatorNumeric(TEST_CELL_MCC + FAKE_MNC);
         verify(mNitzStateMachine, times(1)).handleCountryDetected("");
     }
+
+    @Test
+    public void testClearCellInfoForLostOperator() {
+        doReturn(true).when(mPhone).isRadioOn();
+        sendServiceState(ServiceState.STATE_OUT_OF_SERVICE);
+        processAllMessages();
+        assertTrue(mLocaleTracker.isTracking());
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+
+        // airplane mode + VoWiFI case
+        doReturn(false).when(mPhone).isRadioOn();
+        sendServiceState(ServiceState.STATE_POWER_OFF);
+        sendServiceState(ServiceState.STATE_IN_SERVICE);
+        assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
index f4c19d9..a41dbe1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
@@ -43,6 +43,7 @@
 
 import android.content.Intent;
 import android.content.res.Resources;
+import android.os.AsyncResult;
 import android.os.HandlerThread;
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
@@ -59,6 +60,7 @@
 
 import com.android.internal.telephony.data.DataSettingsManager;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.test.SimulatedCommands;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -302,9 +304,18 @@
 
     @Test
     public void testSubInfoChangeAfterRadioUnavailable() throws Exception {
+        int phone1SubId = 1;
+        int phone2SubId = 2;
+        // Mock DSDS, mock Phone 2
+        SimulatedCommands simulatedCommands2 = mock(SimulatedCommands.class);
+        mPhone2.mCi = simulatedCommands2;
+        doReturn(mDataSettingsManagerMock2).when(mPhone2).getDataSettingsManager();
+        mPhones = new Phone[]{mPhone, mPhone2};
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+        // Load carrier config for all subs
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
-        sendCarrierConfigChanged(0, 1);
-        sendCarrierConfigChanged(1, 2);
+        sendCarrierConfigChanged(0, phone1SubId);
+        sendCarrierConfigChanged(1, phone2SubId);
         processAllMessages();
 
         // Ensure all subscription loaded only updates state once
@@ -315,7 +326,22 @@
         verify(mSubscriptionManagerService, never()).setDefaultVoiceSubId(anyInt());
         verify(mSubscriptionManagerService, never()).setDefaultSmsSubId(anyInt());
 
-        // Notify radio unavailable.
+        // DSDS -> single active modem, radio available on phone 0 but unavailable on phone 1
+        doReturn(TelephonyManager.RADIO_POWER_UNAVAILABLE).when(simulatedCommands2).getRadioState();
+        markSubscriptionInactive(phone2SubId);
+        AsyncResult result = new AsyncResult(null, 1/*activeModemCount*/, null);
+        clearInvocations(mSubscriptionManagerService);
+        mMultiSimSettingControllerUT.obtainMessage(
+                MultiSimSettingController.EVENT_MULTI_SIM_CONFIG_CHANGED, result).sendToTarget();
+        mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
+        processAllMessages();
+
+        // Should still set defaults to the only remaining sub
+        verify(mSubscriptionManagerService).setDefaultDataSubId(phone1SubId);
+        verify(mSubscriptionManagerService).setDefaultVoiceSubId(phone1SubId);
+        verify(mSubscriptionManagerService).setDefaultSmsSubId(phone1SubId);
+
+        // Notify radio unavailable on all subs.
         replaceInstance(BaseCommands.class, "mState", mSimulatedCommands,
                 TelephonyManager.RADIO_POWER_UNAVAILABLE);
         mMultiSimSettingControllerUT.obtainMessage(
@@ -323,7 +349,6 @@
 
         // Mark all subs as inactive.
         markSubscriptionInactive(1);
-        markSubscriptionInactive(2);
         clearInvocations(mSubscriptionManagerService);
 
         // The below sub info change should be ignored.
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java
index 8dc8ea7..f5bdd27 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertEquals;
 
 import android.compat.testing.PlatformCompatChangeRule;
@@ -54,6 +56,7 @@
                 .setAvailableServices(Arrays.asList(NetworkRegistrationInfo.SERVICE_TYPE_DATA))
                 .setCellIdentity(new CellIdentityLte())
                 .setRegisteredPlmn("12345")
+                .setIsNonTerrestrialNetwork(true)
                 .build();
 
         Parcel p = Parcel.obtain();
@@ -70,6 +73,7 @@
     public void testDefaultValues() {
         NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder().build();
         assertEquals("", nri.getRegisteredPlmn());
+        assertThat(nri.isNonTerrestrialNetwork()).isEqualTo(false);
     }
 
     @Test
@@ -77,6 +81,8 @@
     public void testBuilder() {
         assertEquals("12345", new NetworkRegistrationInfo.Builder()
                 .setRegisteredPlmn("12345").build().getRegisteredPlmn());
+        assertThat(new NetworkRegistrationInfo.Builder().setIsNonTerrestrialNetwork(true).build()
+                .isNonTerrestrialNetwork()).isEqualTo(true);
     }
 
     @Test
@@ -139,4 +145,11 @@
         assertEquals(NetworkRegistrationInfo.REGISTRATION_STATE_EMERGENCY,
                 nri.getRegistrationState());
     }
+
+    @Test
+    public void testSetIsNonTerrestrialNetwork() {
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder().build();
+        nri.setIsNonTerrestrialNetwork(true);
+        assertThat(nri.isNonTerrestrialNetwork()).isEqualTo(true);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
index 4b91207..7f15af8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
@@ -34,6 +34,7 @@
 import android.os.PersistableBundle;
 import android.os.PowerManager;
 import android.telephony.CarrierConfigManager;
+import android.telephony.CellInfo;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.RadioAccessFamily;
@@ -282,7 +283,7 @@
 
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         setPhysicalLinkStatus(false);
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
@@ -330,7 +331,7 @@
 
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         setPhysicalLinkStatus(true);
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_con", getCurrentState().getName());
@@ -386,7 +387,9 @@
         mBundle.putIntArray(CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY,
                 new int[]{41});
         PhysicalChannelConfig physicalChannelConfig = new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(1)
                 .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
                 .setBand(41)
                 .build();
         List<PhysicalChannelConfig> lastPhysicalChannelConfigList = new ArrayList<>();
@@ -394,7 +397,36 @@
         doReturn(lastPhysicalChannelConfigList).when(mSST).getPhysicalChannelConfigList();
         sendCarrierConfigChanged();
 
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
+        processAllMessages();
+        assertEquals("connected_mmwave", getCurrentState().getName());
+    }
+
+    @Test
+    public void testTransitionToCurrentStateNrConnectedMmwaveWithAdditionalBandAndNoMmwaveNrNsa()
+            throws Exception {
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(mServiceState).getNrFrequencyRange();
+        mBundle.putIntArray(CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY,
+                new int[]{41});
+        PhysicalChannelConfig ltePhysicalChannelConfig = new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(1)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .build();
+        PhysicalChannelConfig nrPhysicalChannelConfig = new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(2)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setCellConnectionStatus(CellInfo.CONNECTION_SECONDARY_SERVING)
+                .setBand(41)
+                .build();
+        List<PhysicalChannelConfig> lastPhysicalChannelConfigList = new ArrayList<>();
+        lastPhysicalChannelConfigList.add(ltePhysicalChannelConfig);
+        lastPhysicalChannelConfigList.add(nrPhysicalChannelConfig);
+        doReturn(lastPhysicalChannelConfigList).when(mSST).getPhysicalChannelConfigList();
+        sendCarrierConfigChanged();
+
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("connected_mmwave", getCurrentState().getName());
@@ -417,7 +449,6 @@
         doReturn(lastPhysicalChannelConfigList).when(mSST).getPhysicalChannelConfigList();
         sendCarrierConfigChanged();
 
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("connected", getCurrentState().getName());
@@ -535,6 +566,169 @@
     }
 
     @Test
+    public void testEventPhysicalChannelConfigChangedWithRatcheting() throws Exception {
+        testTransitionToCurrentStateNrConnected();
+        mBundle.putIntArray(CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY,
+                new int[]{41, 77});
+        mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
+        mBundle.putBoolean(CarrierConfigManager.KEY_RATCHET_NR_ADVANCED_BANDWIDTH_IF_RRC_IDLE_BOOL,
+                true);
+        sendCarrierConfigChanged();
+
+        // Primary serving NR PCC with cell ID = 1, band = none, bandwidth = 200000
+        PhysicalChannelConfig pcc1 = new PhysicalChannelConfig.Builder()
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setPhysicalCellId(1)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .setCellBandwidthDownlinkKhz(19999)
+                .build();
+        // Secondary serving NR PCC with cell ID = 2, band = 41, bandwidth = 10000
+        PhysicalChannelConfig pcc2 = new PhysicalChannelConfig.Builder()
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setPhysicalCellId(2)
+                .setCellConnectionStatus(CellInfo.CONNECTION_SECONDARY_SERVING)
+                .setCellBandwidthDownlinkKhz(10000)
+                .setBand(41)
+                .build();
+        // Primary serving NR PCC with cell ID = 3, band = 77, bandwidth = 0
+        PhysicalChannelConfig pcc3 = new PhysicalChannelConfig.Builder()
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setPhysicalCellId(3)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .setBand(77)
+                .build();
+
+        List<PhysicalChannelConfig> physicalChannelConfigs = new ArrayList<>();
+        physicalChannelConfigs.add(pcc1);
+        physicalChannelConfigs.add(pcc2);
+        doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
+
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        processAllMessages();
+        assertEquals("connected_mmwave", getCurrentState().getName());
+
+        // bands and bandwidths should stay ratcheted even if an empty PCC list is sent
+        doReturn(new ArrayList<>()).when(mSST).getPhysicalChannelConfigList();
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        processAllMessages();
+        assertEquals("connected_mmwave", getCurrentState().getName());
+
+        // bands and bandwidths should stay ratcheted as long as anchor NR cell is the same
+        physicalChannelConfigs.remove(pcc2);
+        doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        processAllMessages();
+        assertEquals("connected_mmwave", getCurrentState().getName());
+
+        // bands and bandwidths should no longer be ratcheted if anchor NR cell changes
+        // add pcc3 to front of list to ensure anchor NR cell changes from 1 -> 3
+        physicalChannelConfigs.add(0, pcc3);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        processAllMessages();
+        assertEquals("connected", getCurrentState().getName());
+
+        physicalChannelConfigs.add(pcc2);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        processAllMessages();
+        assertEquals("connected_mmwave", getCurrentState().getName());
+    }
+
+    @Test
+    public void testEventPhysicalChannelConfigChangedWithoutRatcheting() throws Exception {
+        testTransitionToCurrentStateNrConnected();
+        mBundle.putIntArray(CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY,
+                new int[]{41, 77});
+        mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
+        sendCarrierConfigChanged();
+
+        // Primary serving NR PCC with cell ID = 1, band = none, bandwidth = 200000
+        PhysicalChannelConfig pcc1 = new PhysicalChannelConfig.Builder()
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setPhysicalCellId(1)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .setCellBandwidthDownlinkKhz(19999)
+                .build();
+        // Secondary serving NR PCC with cell ID = 2, band = 41, bandwidth = 10000
+        PhysicalChannelConfig pcc2 = new PhysicalChannelConfig.Builder()
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setPhysicalCellId(2)
+                .setCellConnectionStatus(CellInfo.CONNECTION_SECONDARY_SERVING)
+                .setCellBandwidthDownlinkKhz(10000)
+                .setBand(41)
+                .build();
+
+        List<PhysicalChannelConfig> physicalChannelConfigs = new ArrayList<>();
+        physicalChannelConfigs.add(pcc1);
+        physicalChannelConfigs.add(pcc2);
+        doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
+
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        processAllMessages();
+        assertEquals("connected_mmwave", getCurrentState().getName());
+
+        // bands and bandwidths should stay ratcheted even if an empty PCC list is sent
+        doReturn(new ArrayList<>()).when(mSST).getPhysicalChannelConfigList();
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        processAllMessages();
+        assertEquals("connected_mmwave", getCurrentState().getName());
+
+        // bands and bandwidths should change if PCC list changes
+        physicalChannelConfigs.remove(pcc2);
+        doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        processAllMessages();
+        assertEquals("connected", getCurrentState().getName());
+    }
+
+    @Test
+    public void testEventPhysicalChannelConfigChangedUsingUserDataForRrc() throws Exception {
+        testTransitionToCurrentStateNrConnected();
+        mBundle.putIntArray(CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY,
+                new int[]{41, 77});
+        mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
+        mBundle.putBoolean(CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL,
+                true);
+        sendCarrierConfigChanged();
+
+        // Primary serving NR PCC with cell ID = 1, band = none, bandwidth = 200000
+        PhysicalChannelConfig pcc1 = new PhysicalChannelConfig.Builder()
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setPhysicalCellId(1)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .setCellBandwidthDownlinkKhz(19999)
+                .build();
+        // Secondary serving NR PCC with cell ID = 2, band = 41, bandwidth = 10000
+        PhysicalChannelConfig pcc2 = new PhysicalChannelConfig.Builder()
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setPhysicalCellId(2)
+                .setCellConnectionStatus(CellInfo.CONNECTION_SECONDARY_SERVING)
+                .setCellBandwidthDownlinkKhz(10000)
+                .setBand(41)
+                .build();
+
+        List<PhysicalChannelConfig> physicalChannelConfigs = new ArrayList<>();
+        physicalChannelConfigs.add(pcc1);
+        physicalChannelConfigs.add(pcc2);
+        doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
+
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        processAllMessages();
+        assertEquals("connected_mmwave", getCurrentState().getName());
+
+        // bands and bandwidths should not stay the same even if an empty PCC list is sent
+        doReturn(new ArrayList<>()).when(mSST).getPhysicalChannelConfigList();
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        processAllMessages();
+        assertEquals("connected", getCurrentState().getName());
+
+        // bands and bandwidths should change if PCC list changes
+        doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        processAllMessages();
+        assertEquals("connected_mmwave", getCurrentState().getName());
+    }
+
+    @Test
     public void testNrPhysicalChannelChangeFromNrConnectedMmwaveToLteConnected() throws Exception {
         testTransitionToCurrentStateNrConnectedMmwave();
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
@@ -556,7 +750,7 @@
         testTransitionToCurrentStateNrConnectedMmwave();
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         setPhysicalLinkStatus(true);
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
 
         processAllMessages();
@@ -592,7 +786,7 @@
 
         doReturn(true).when(mServiceState).isUsingCarrierAggregation();
         doReturn(new int[] {30000}).when(mServiceState).getCellBandwidths();
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
@@ -620,7 +814,7 @@
         // LTE -> LTE+
         doReturn(true).when(mServiceState).isUsingCarrierAggregation();
         doReturn(new int[] {30000}).when(mServiceState).getCellBandwidths();
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
@@ -648,7 +842,7 @@
         // LTE -> LTE+
         doReturn(true).when(mServiceState).isUsingCarrierAggregation();
         doReturn(new int[] {30000}).when(mServiceState).getCellBandwidths();
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
@@ -676,7 +870,7 @@
         testTransitionToCurrentStateLteConnectedSupportPhysicalChannelConfig1_6();
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
         setPhysicalLinkStatus(false);
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
@@ -1277,27 +1471,9 @@
         List<PhysicalChannelConfig> lastPhysicalChannelConfigList = new ArrayList<>();
         lastPhysicalChannelConfigList.add(new PhysicalChannelConfig.Builder()
                 .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
-                .setCellBandwidthDownlinkKhz(20001)
-                .build());
-        doReturn(lastPhysicalChannelConfigList).when(mSST).getPhysicalChannelConfigList();
-        mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
-        sendCarrierConfigChanged();
-
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
-        processAllMessages();
-        assertEquals("connected_mmwave", getCurrentState().getName());
-    }
-
-    @Test
-    public void testTransitionToCurrentStateNrConnectedWithHighBandwidthIncludingLte()
-            throws Exception {
-        assertEquals("DefaultState", getCurrentState().getName());
-        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
-        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
-        List<PhysicalChannelConfig> lastPhysicalChannelConfigList = new ArrayList<>();
-        lastPhysicalChannelConfigList.add(new PhysicalChannelConfig.Builder()
-                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
-                .setCellBandwidthDownlinkKhz(20000)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .setPhysicalCellId(1)
+                .setCellBandwidthDownlinkKhz(19999)
                 .build());
         lastPhysicalChannelConfigList.add(new PhysicalChannelConfig.Builder()
                 .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE)
@@ -1305,13 +1481,13 @@
                 .build());
         doReturn(lastPhysicalChannelConfigList).when(mSST).getPhysicalChannelConfigList();
         mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
+        sendCarrierConfigChanged();
+        assertEquals("connected", getCurrentState().getName());
+
         mBundle.putBoolean(
                 CarrierConfigManager.KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL,
                 true);
         sendCarrierConfigChanged();
-
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
-        processAllMessages();
         assertEquals("connected_mmwave", getCurrentState().getName());
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
index 500d69c..7efb886 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
@@ -18,6 +18,8 @@
 
 import static android.telephony.ServiceState.UNKNOWN_ID;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.os.Bundle;
 import android.os.Parcel;
 import android.telephony.AccessNetworkConstants;
@@ -444,6 +446,18 @@
         assertEquals(UNKNOWN_ID, coarseLocationSanitizedSs.getCdmaNetworkId());
     }
 
+    @SmallTest
+    public void testIsUsingNonTerrestrialNetwork() {
+        ServiceState ss = new ServiceState();
+        assertThat(ss.isUsingNonTerrestrialNetwork()).isEqualTo(false);
+
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setIsNonTerrestrialNetwork(true)
+                .build();
+        ss.addNetworkRegistrationInfo(nri);
+        assertThat(ss.isUsingNonTerrestrialNetwork()).isEqualTo(true);
+    }
+
     private void assertCellIdentitiesSanitized(ServiceState ss) {
         List<NetworkRegistrationInfo> networkRegistrationInfoList =
                 ss.getNetworkRegistrationInfoList();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
index 846b48e..5f592d1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY;
+import static android.telephony.NetworkRegistrationInfo.SERVICE_TYPE_SMS;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -96,6 +99,7 @@
 import com.android.internal.telephony.data.AccessNetworksManager;
 import com.android.internal.telephony.data.DataNetworkController;
 import com.android.internal.telephony.metrics.ServiceStateStats;
+import com.android.internal.telephony.satellite.SatelliteController;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.test.SimulatedCommands;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus;
@@ -116,6 +120,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
 
 public class ServiceStateTrackerTest extends TelephonyTest {
     // Mocked classes
@@ -138,6 +143,7 @@
     private ServiceStateTracker sst;
     private ServiceStateTrackerTestHandler mSSTTestHandler;
     private PersistableBundle mBundle;
+    private SatelliteController mSatelliteController;
 
     private static final int EVENT_REGISTERED_TO_NETWORK = 1;
     private static final int EVENT_SUBSCRIPTION_INFO_READY = 2;
@@ -248,6 +254,11 @@
         mSubInfoInternal = new SubscriptionInfoInternal.Builder().setId(1).build();
         mServiceStateStats = Mockito.mock(ServiceStateStats.class);
 
+        mSatelliteController = Mockito.mock(SatelliteController.class);
+        replaceInstance(SatelliteController.class, "sInstance", null,
+                mSatelliteController);
+        doReturn(new ArrayList<>()).when(mSatelliteController).getSatellitePlmnList();
+
         mContextFixture.putResource(R.string.kg_text_message_separator, " \u2014 ");
 
         doReturn(mSubInfoInternal).when(mSubscriptionManagerService)
@@ -2763,6 +2774,11 @@
         ss.setEmergencyOnly(true);
         sst.mSS = ss;
 
+        // The other phone is in service
+        ss = new ServiceState();
+        doReturn(ss).when(mSST).getServiceState();
+        doReturn(ServiceState.STATE_IN_SERVICE).when(mSST).getCombinedRegState(ss);
+
         // update the spn
         sst.updateSpnDisplay();
 
@@ -3208,4 +3224,55 @@
 
         assertTrue(sst.mSS.isIwlanPreferred());
     }
+
+    @Test
+    public void testRegisterToSatellite() {
+        int[] satelliteSupportedServices = {SERVICE_TYPE_SMS, SERVICE_TYPE_EMERGENCY};
+        List<Integer> satelliteSupportedServiceList =
+                Arrays.stream(satelliteSupportedServices).boxed().collect(Collectors.toList());
+        CellIdentityGsm cellIdentity =
+                new CellIdentityGsm(0, 1, 900, 5, "101", "23", "test", "tst",
+                        Collections.emptyList());
+        doReturn(Arrays.asList("10123")).when(mSatelliteController).getSatellitePlmnList();
+        doReturn(satelliteSupportedServiceList).when(mSatelliteController)
+                .getSupportedSatelliteServices(sst.mSubId, "10123");
+
+        assertFalse(sst.mSS.isUsingNonTerrestrialNetwork());
+
+        // Data registered to satellite roaming PLMN - "00101"
+        NetworkRegistrationInfo dataReg = new NetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                5, 16, 0, false, null, cellIdentity, getPlmnFromCellIdentity(cellIdentity),
+                1, false, false, false, null);
+
+        // CS out of service
+        NetworkRegistrationInfo voiceReg = new NetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                0, 16, 0, true, null, cellIdentity, getPlmnFromCellIdentity(cellIdentity),
+                false, 0, 0, 0);
+
+        sst.mPollingContext[0] = 2;
+        // Update voice reg state to be in oos
+        sst.sendMessage(sst.obtainMessage(
+                ServiceStateTracker.EVENT_POLL_STATE_CS_CELLULAR_REGISTRATION,
+                new AsyncResult(sst.mPollingContext, voiceReg, null)));
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+
+        // Update data registered to satellite roaming PLMN
+        sst.sendMessage(sst.obtainMessage(
+                ServiceStateTracker.EVENT_POLL_STATE_PS_CELLULAR_REGISTRATION,
+                new AsyncResult(sst.mPollingContext, dataReg, null)));
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+
+        assertTrue(sst.mSS.isUsingNonTerrestrialNetwork());
+        List<NetworkRegistrationInfo> nriList =
+                sst.mSS.getNetworkRegistrationInfoListForTransportType(
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        assertEquals(2, nriList.size());
+        for (NetworkRegistrationInfo nri : nriList) {
+            assertTrue(Arrays.equals(satelliteSupportedServices, nri.getAvailableServices().stream()
+                    .mapToInt(Integer::intValue)
+                    .toArray()));
+        }
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index b044814..705bafd 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -219,6 +219,7 @@
     protected UiccCardApplication mUiccCardApplication3gpp2;
     protected UiccCardApplication mUiccCardApplicationIms;
     protected SIMRecords mSimRecords;
+    protected SignalStrengthController mSignalStrengthController;
     protected RuimRecords mRuimRecords;
     protected IsimUiccRecords mIsimUiccRecords;
     protected ProxyController mProxyController;
@@ -455,6 +456,7 @@
         mUiccCardApplication3gpp2 = Mockito.mock(UiccCardApplication.class);
         mUiccCardApplicationIms = Mockito.mock(UiccCardApplication.class);
         mSimRecords = Mockito.mock(SIMRecords.class);
+        mSignalStrengthController = Mockito.mock(SignalStrengthController.class);
         mRuimRecords = Mockito.mock(RuimRecords.class);
         mIsimUiccRecords = Mockito.mock(IsimUiccRecords.class);
         mProxyController = Mockito.mock(ProxyController.class);
@@ -636,6 +638,7 @@
         doReturn(mSST).when(mPhone).getServiceStateTracker();
         doReturn(mDeviceStateMonitor).when(mPhone).getDeviceStateMonitor();
         doReturn(mDisplayInfoController).when(mPhone).getDisplayInfoController();
+        doReturn(mSignalStrengthController).when(mPhone).getSignalStrengthController();
         doReturn(mEmergencyNumberTracker).when(mPhone).getEmergencyNumberTracker();
         doReturn(mCarrierSignalAgent).when(mPhone).getCarrierSignalAgent();
         doReturn(mCarrierActionAgent).when(mPhone).getCarrierActionAgent();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java
new file mode 100644
index 0000000..7ac3a17
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2023 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.internal.telephony.data;
+
+import static android.telephony.SubscriptionManager.DEFAULT_PHONE_INDEX;
+
+import static com.android.internal.telephony.data.AutoDataSwitchController.EVALUATION_REASON_DATA_SETTINGS_CHANGED;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.net.NetworkCapabilities;
+import android.os.AsyncResult;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class AutoDataSwitchControllerTest extends TelephonyTest {
+    private static final int EVENT_SERVICE_STATE_CHANGED = 1;
+    private static final int EVENT_DISPLAY_INFO_CHANGED = 2;
+    private static final int EVENT_EVALUATE_AUTO_SWITCH = 3;
+    private static final int EVENT_SIGNAL_STRENGTH_CHANGED = 4;
+    private static final int EVENT_MEETS_AUTO_DATA_SWITCH_STATE = 5;
+
+    private static final int PHONE_1 = 0;
+    private static final int SUB_1 = 1;
+    private static final int PHONE_2 = 1;
+    private static final int SUB_2 = 2;
+    private static final int MAX_RETRY = 5;
+    // Mocked
+    private AutoDataSwitchController.AutoDataSwitchControllerCallback mMockedPhoneSwitcherCallback;
+
+    private int mDefaultDataSub;
+    private AutoDataSwitchController mAutoDataSwitchControllerUT;
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        mMockedPhoneSwitcherCallback =
+                mock(AutoDataSwitchController.AutoDataSwitchControllerCallback.class);
+
+        doReturn(PHONE_1).when(mPhone).getPhoneId();
+        doReturn(SUB_1).when(mPhone).getSubId();
+
+        doReturn(PHONE_2).when(mPhone2).getPhoneId();
+        doReturn(SUB_2).when(mPhone2).getSubId();
+
+        doReturn(SUB_1).when(mSubscriptionManagerService).getSubId(PHONE_1);
+        doReturn(SUB_2).when(mSubscriptionManagerService).getSubId(PHONE_2);
+
+        mPhones = new Phone[]{mPhone, mPhone2};
+        for (Phone phone : mPhones) {
+            doReturn(mSST).when(phone).getServiceStateTracker();
+            doReturn(mDisplayInfoController).when(phone).getDisplayInfoController();
+            doReturn(mSignalStrengthController).when(phone).getSignalStrengthController();
+            doReturn(mSignalStrength).when(phone).getSignalStrength();
+            doAnswer(invocation -> phone.getSubId() == mDefaultDataSub)
+                    .when(phone).isUserDataEnabled();
+        }
+        doReturn(new int[mPhones.length]).when(mSubscriptionManagerService)
+                .getActiveSubIdList(true);
+        doAnswer(invocation -> {
+            int subId = (int) invocation.getArguments()[0];
+
+            if (!SubscriptionManager.isUsableSubIdValue(subId)) return null;
+
+            int slotIndex = subId == SUB_1 ? PHONE_1 : PHONE_2;
+            return new SubscriptionInfoInternal.Builder()
+                    .setSimSlotIndex(slotIndex).setId(subId).build();
+        }).when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+
+        // Change resource overlay
+        doReturn(true).when(mDataConfigManager).isPingTestBeforeAutoDataSwitchRequired();
+        doReturn(1L).when(mDataConfigManager)
+                .getAutoDataSwitchAvailabilityStabilityTimeThreshold();
+        doReturn(MAX_RETRY).when(mDataConfigManager).getAutoDataSwitchValidationMaxRetry();
+
+        setDefaultDataSubId(SUB_1);
+        doReturn(PHONE_1).when(mPhoneSwitcher).getPreferredDataPhoneId();
+
+        mAutoDataSwitchControllerUT = new AutoDataSwitchController(mContext, Looper.myLooper(),
+                mPhoneSwitcher, mMockedPhoneSwitcherCallback);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mAutoDataSwitchControllerUT = null;
+        super.tearDown();
+    }
+
+    @Test
+    public void testCancelSwitch_onPrimary() {
+        // 0. When all conditions met
+        prepareIdealUsesNonDdsCondition();
+        processAllFutureMessages();
+
+        // Verify attempting to switch
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(PHONE_2, true/*needValidation*/);
+
+        // 1. Service state becomes not ideal - primary is available again
+        serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireCancelAnyPendingAutoSwitchValidation();
+
+        // 2.1 User data disabled on primary SIM
+        prepareIdealUsesNonDdsCondition();
+        processAllFutureMessages();
+        clearInvocations(mMockedPhoneSwitcherCallback);
+        doReturn(false).when(mPhone).isUserDataEnabled();
+        mAutoDataSwitchControllerUT.evaluateAutoDataSwitch(EVALUATION_REASON_DATA_SETTINGS_CHANGED);
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireCancelAnyPendingAutoSwitchValidation();
+
+        // 2.2 Auto switch feature is disabled
+        prepareIdealUsesNonDdsCondition();
+        processAllFutureMessages();
+        clearInvocations(mMockedPhoneSwitcherCallback);
+        doReturn(false).when(mPhone2).isDataAllowed();
+        mAutoDataSwitchControllerUT.evaluateAutoDataSwitch(EVALUATION_REASON_DATA_SETTINGS_CHANGED);
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireCancelAnyPendingAutoSwitchValidation();
+
+        // 3.1 No default network
+        prepareIdealUsesNonDdsCondition();
+        processAllFutureMessages();
+        clearInvocations(mMockedPhoneSwitcherCallback);
+        mAutoDataSwitchControllerUT.updateDefaultNetworkCapabilities(new NetworkCapabilities()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI));
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireCancelAnyPendingAutoSwitchValidation();
+    }
+
+    @Test
+    public void testOnNonDdsSwitchBackToPrimary() {
+        doReturn(PHONE_2).when(mPhoneSwitcher).getPreferredDataPhoneId();
+
+        prepareIdealUsesNonDdsCondition();
+        // 1.1 service state changes - primary becomes available again, require validation
+        serviceStateChanged(PHONE_1,
+                NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING/*need validate*/);
+        processAllFutureMessages();
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
+                true/*needValidation*/);
+
+        clearInvocations(mMockedPhoneSwitcherCallback);
+        prepareIdealUsesNonDdsCondition();
+        // 1.2 service state changes - secondary becomes unavailable, NO need validation
+        serviceStateChanged(PHONE_1,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME/*need validate*/);
+        serviceStateChanged(PHONE_2, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING/*no need*/);
+        processAllFutureMessages();
+        // The later validation requirement overrides the previous
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
+                false/*needValidation*/);
+
+        clearInvocations(mMockedPhoneSwitcherCallback);
+        prepareIdealUsesNonDdsCondition();
+        // 2.1 User data disabled on primary SIM, no need validation
+        doReturn(false).when(mPhone).isUserDataEnabled();
+        mAutoDataSwitchControllerUT.evaluateAutoDataSwitch(EVALUATION_REASON_DATA_SETTINGS_CHANGED);
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireImmediatelySwitchToPhone(DEFAULT_PHONE_INDEX,
+                EVALUATION_REASON_DATA_SETTINGS_CHANGED);
+
+        clearInvocations(mMockedPhoneSwitcherCallback);
+        prepareIdealUsesNonDdsCondition();
+        // 2.2 Auto switch feature is disabled, no need validation
+        clearInvocations(mCellularNetworkValidator);
+        doReturn(false).when(mPhone2).isDataAllowed();
+        mAutoDataSwitchControllerUT.evaluateAutoDataSwitch(EVALUATION_REASON_DATA_SETTINGS_CHANGED);
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireImmediatelySwitchToPhone(DEFAULT_PHONE_INDEX,
+                EVALUATION_REASON_DATA_SETTINGS_CHANGED);
+
+        clearInvocations(mMockedPhoneSwitcherCallback);
+        prepareIdealUsesNonDdsCondition();
+        // 3.1 Default network is active on non-cellular transport
+        mAutoDataSwitchControllerUT.updateDefaultNetworkCapabilities(new NetworkCapabilities()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI));
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
+                false/*needValidation*/);
+    }
+
+    @Test
+    public void testCancelSwitch_onSecondary() {
+        doReturn(PHONE_2).when(mPhoneSwitcher).getPreferredDataPhoneId();
+        prepareIdealUsesNonDdsCondition();
+
+        // attempts the switch back due to secondary becomes ROAMING
+        serviceStateChanged(PHONE_2, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
+                false/*needValidation*/);
+
+        // cancel the switch back attempt due to secondary back to HOME
+        serviceStateChanged(PHONE_2, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireCancelAnyPendingAutoSwitchValidation();
+    }
+
+    @Test
+    public void testValidationFailedRetry() {
+        prepareIdealUsesNonDdsCondition();
+
+        for (int i = 0; i < MAX_RETRY; i++) {
+            mAutoDataSwitchControllerUT.evaluateRetryOnValidationFailed();
+            processAllFutureMessages();
+        }
+        verify(mMockedPhoneSwitcherCallback, times(MAX_RETRY))
+                .onRequireValidation(PHONE_2, true /*need validation*/);
+    }
+
+    @Test
+    public void testExemptPingTest() {
+        // Change resource overlay
+        doReturn(false).when(mDataConfigManager)
+                .isPingTestBeforeAutoDataSwitchRequired();
+        mAutoDataSwitchControllerUT = new AutoDataSwitchController(mContext, Looper.myLooper(),
+                mPhoneSwitcher, mMockedPhoneSwitcherCallback);
+
+        //1. DDS -> nDDS, verify callback doesn't require validation
+        prepareIdealUsesNonDdsCondition();
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(PHONE_2, false/*needValidation*/);
+
+        //2. nDDS -> DDS, verify callback doesn't require validation
+        doReturn(PHONE_2).when(mPhoneSwitcher).getPreferredDataPhoneId();
+        serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+        processAllFutureMessages();
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
+                false/*needValidation*/);
+    }
+
+    @Test
+    public void testSetNotification() {
+        NotificationManager notificationManager = (NotificationManager)
+                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        SubscriptionInfo mockedInfo = mock(SubscriptionInfo.class);
+        doReturn(false).when(mockedInfo).isOpportunistic();
+        doReturn(mockedInfo).when(mSubscriptionManagerService).getSubscriptionInfo(anyInt());
+
+        // First switch is not due to auto, so no notification.
+        mAutoDataSwitchControllerUT.displayAutoDataSwitchNotification(PHONE_2, false);
+        verify(mSubscriptionManagerService, never()).getSubscriptionInfo(SUB_2);
+
+        // Switch is due to auto, show notification.
+        mAutoDataSwitchControllerUT.displayAutoDataSwitchNotification(PHONE_2, true);
+        verify(notificationManager).notify(any(), anyInt(), any());
+        verify(mSubscriptionManagerService).getSubscriptionInfo(SUB_2);
+
+        // Switch is due to auto, but already shown notification, hide the notification.
+        mAutoDataSwitchControllerUT.displayAutoDataSwitchNotification(PHONE_2, true);
+        verify(notificationManager).cancel(any(), anyInt());
+    }
+
+    @Test
+    public void testMultiSimConfigChanged() {
+        // Test Dual -> Single
+        mAutoDataSwitchControllerUT.onMultiSimConfigChanged(1);
+
+        verify(mDisplayInfoController).unregisterForTelephonyDisplayInfoChanged(any());
+        verify(mSignalStrengthController).unregisterForSignalStrengthChanged(any());
+        verify(mSST).unregisterForServiceStateChanged(any());
+
+        clearInvocations(mDisplayInfoController, mSignalStrengthController, mSST);
+        // Test Single -> Dual
+        mAutoDataSwitchControllerUT.onMultiSimConfigChanged(2);
+
+        verify(mDisplayInfoController).registerForTelephonyDisplayInfoChanged(any(),
+                eq(EVENT_DISPLAY_INFO_CHANGED), eq(PHONE_2));
+        verify(mSignalStrengthController).registerForSignalStrengthChanged(any(),
+                eq(EVENT_SIGNAL_STRENGTH_CHANGED), eq(PHONE_2));
+        verify(mSST).registerForServiceStateChanged(any(),
+                eq(EVENT_SERVICE_STATE_CHANGED), eq(PHONE_2));
+    }
+
+    /**
+     * Trigger conditions
+     * 1. service state changes
+     * 2. data setting changes
+     *      - user toggle data
+     *      - user toggle auto switch feature
+     * 3. default network changes
+     *      - current network lost
+     *      - network become active on non-cellular network
+     */
+    private void prepareIdealUsesNonDdsCondition() {
+        // 1. service state changes
+        serviceStateChanged(PHONE_2, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        serviceStateChanged(PHONE_1, NetworkRegistrationInfo
+                .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING);
+
+        // 2.1 User data enabled on primary SIM
+        doReturn(true).when(mPhone).isUserDataEnabled();
+
+        // 2.2 Auto switch feature is enabled
+        doReturn(true).when(mPhone2).isDataAllowed();
+
+        // 3.1 No default network
+        mAutoDataSwitchControllerUT.updateDefaultNetworkCapabilities(null /*networkCapabilities*/);
+    }
+
+    private void serviceStateChanged(int phoneId,
+            @NetworkRegistrationInfo.RegistrationState int dataRegState) {
+
+        ServiceState ss = new ServiceState();
+
+        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setRegistrationState(dataRegState)
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .build());
+
+        ss.setDataRoamingFromRegistration(dataRegState
+                == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+
+        doReturn(ss).when(mPhones[phoneId]).getServiceState();
+
+        Message msg = mAutoDataSwitchControllerUT.obtainMessage(EVENT_SERVICE_STATE_CHANGED);
+        msg.obj = new AsyncResult(phoneId, null, null);
+        mAutoDataSwitchControllerUT.sendMessage(msg);
+    }
+    private void setDefaultDataSubId(int defaultDataSub) {
+        mDefaultDataSub = defaultDataSub;
+        doReturn(mDefaultDataSub).when(mSubscriptionManagerService).getDefaultDataSubId();
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java
index f312808..d4a0804 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java
@@ -20,10 +20,16 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.os.Looper;
 import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyDisplayInfo;
+import android.telephony.TelephonyManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -96,4 +102,47 @@
         assertThat(invalidFormat3.timeWindow).isEqualTo(defaultValue.timeWindow);
         assertThat(invalidFormat3.eventNumOccurrence).isEqualTo(defaultValue.eventNumOccurrence);
     }
+
+    @Test
+    public void testParseAutoDataSwitchScoreTable() {
+        SignalStrength signalStrength = mock(SignalStrength.class);
+        int tolerance = 100;
+        PersistableBundle auto_data_switch_rat_signal_score_string_bundle = new PersistableBundle();
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "NR_NSA_MMWAVE", new int[]{10000, 10227, 12488, 15017, 15278});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "LTE", new int[]{-3731, 5965, 8618, 11179, 13384});
+        mBundle.putPersistableBundle(
+                CarrierConfigManager.KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_BUNDLE,
+                auto_data_switch_rat_signal_score_string_bundle);
+
+        mContextFixture.putIntResource(com.android.internal.R.integer
+                .auto_data_switch_score_tolerance, tolerance);
+
+        mDataConfigManagerUT.sendEmptyMessage(1/*EVENT_CARRIER_CONFIG_CHANGED*/);
+        processAllMessages();
+
+        assertThat(mDataConfigManagerUT.getAutoDataSwitchScoreTolerance()).isEqualTo(tolerance);
+
+        // Verify NSA_MMWAVE
+        doReturn(SignalStrength.SIGNAL_STRENGTH_POOR).when(signalStrength).getLevel();
+        assertThat(mDataConfigManagerUT.getAutoDataSwitchScore(new TelephonyDisplayInfo(
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, false/*isRoaming*/),
+                signalStrength)).isEqualTo(10227);
+        // Verify if entry contains any invalid negative scores, should yield -1.
+        doReturn(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN).when(signalStrength).getLevel();
+        assertThat(mDataConfigManagerUT.getAutoDataSwitchScore(new TelephonyDisplayInfo(
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false/*isRoaming*/),
+                signalStrength))
+                .isEqualTo(-1/*INVALID_AUTO_DATA_SWITCH_SCORE*/);
+        // Verify non-existent entry should yield -1
+        doReturn(SignalStrength.SIGNAL_STRENGTH_POOR).when(signalStrength).getLevel();
+        assertThat(mDataConfigManagerUT.getAutoDataSwitchScore(new TelephonyDisplayInfo(
+                        TelephonyManager.NETWORK_TYPE_EDGE,
+                        TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false/*isRoaming*/),
+                signalStrength))
+                .isEqualTo(-1/*INVALID_AUTO_DATA_SWITCH_SCORE*/);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
index 808a15d..d96bac4 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
@@ -35,6 +35,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
@@ -62,6 +63,7 @@
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.AccessNetworkConstants.TransportType;
+import android.telephony.Annotation;
 import android.telephony.Annotation.DataFailureCause;
 import android.telephony.Annotation.NetCapability;
 import android.telephony.Annotation.NetworkType;
@@ -1584,6 +1586,14 @@
 
         // Verify data is torn down.
         verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+
+        // Registration is back to HOME.
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        processAllFutureMessages();
+
+        // Verify data is restored.
+        verifyInternetConnected();
     }
 
     @Test
@@ -1630,13 +1640,27 @@
         doReturn(true).when(controller).isCarrierConfigLoadedForAllSub();
         replaceInstance(MultiSimSettingController.class, "sInstance", null, controller);
 
+        // Mock Data Overall data is always enabled due to auto data switch,
+        // verify the test shouldn't rely on the overall data status
+        doReturn(1).when(mPhone).getSubId();
+        doReturn(2).when(mSubscriptionManagerService).getDefaultDataSubId();
+        Phone phone2 = Mockito.mock(Phone.class);
+        phone2.mCi = mSimulatedCommands;
+        doReturn(true).when(phone2).isUserDataEnabled();
+        doReturn(mDataSettingsManager).when(phone2).getDataSettingsManager();
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[]{mPhone, phone2});
+        mDataNetworkControllerUT.getDataSettingsManager().setMobileDataPolicy(TelephonyManager
+                .MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, true);
+        processAllMessages();
+        clearInvocations(mPhone);
+
         controller.notifyAllSubscriptionLoaded();
         mDataNetworkControllerUT.getDataSettingsManager().setDataEnabled(
                 TelephonyManager.DATA_ENABLED_REASON_USER, !isDataEnabled,
                 mContext.getOpPackageName());
         processAllMessages();
 
-        // Verify not to notify MultiSimSettingController
+        // Verify not to notify MultiSimSettingController due to internal calling package
         verify(controller, never()).notifyUserDataEnabled(anyInt(), anyBoolean());
 
         mDataNetworkControllerUT.getDataSettingsManager().setDataEnabled(
@@ -1644,7 +1668,7 @@
                 mContext.getOpPackageName());
         processAllMessages();
 
-        // Verify not to notify MultiSimSettingController
+        // Verify not to notify MultiSimSettingController due to internal calling package
         verify(controller, never()).notifyUserDataEnabled(anyInt(), anyBoolean());
 
         mDataNetworkControllerUT.getDataSettingsManager().setDataEnabled(
@@ -1655,6 +1679,7 @@
 
         // Verify to notify MultiSimSettingController exactly 2 times
         verify(controller, times(2)).notifyUserDataEnabled(anyInt(), anyBoolean());
+        verify(mPhone, never()).notifyDataEnabled(anyBoolean(), anyInt());
     }
 
     @Test
@@ -3208,6 +3233,14 @@
         processAllMessages();
 
         verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_DUN);
+
+        // User data disabled
+        mDataNetworkControllerUT.getDataSettingsManager().setDataEnabled(
+                TelephonyManager.DATA_ENABLED_REASON_USER, false, mContext.getOpPackageName());
+        processAllMessages();
+
+        // Everything should be disconnected.
+        verifyAllDataDisconnected();
     }
 
     @Test
@@ -3869,6 +3902,84 @@
     }
 
     @Test
+    public void testHandoverDataNetworkRoamingOos() throws Exception {
+        testSetupImsDataNetwork();
+        // Configured handover is disallowed at Roaming.
+        mCarrierConfig.putStringArray(
+                CarrierConfigManager.KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY,
+                new String[]{
+                        "source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN|UNKNOWN, "
+                                + "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, roaming=true, "
+                                + "type=disallowed, capabilities=IMS"
+                });
+        carrierConfigChanged();
+        DataNetwork dataNetwork = getDataNetworks().get(0);
+        //Enter ROAMING
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+        updateServiceStateForDatatNetwork(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING, dataNetwork);
+        //OOS
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING);
+        updateServiceStateForDatatNetwork(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING,
+                dataNetwork);
+
+        updateTransport(NetworkCapabilities.NET_CAPABILITY_IMS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        // Verify IMS network was torn down on source first.
+        verify(mMockedWwanDataServiceManager).deactivateDataCall(anyInt(),
+                eq(DataService.REQUEST_REASON_NORMAL), any(Message.class));
+
+        // Verify that IWLAN is brought up again on IWLAN.
+        verify(mMockedWlanDataServiceManager).setupDataCall(anyInt(),
+                any(DataProfile.class), anyBoolean(), anyBoolean(),
+                eq(DataService.REQUEST_REASON_NORMAL), any(), anyInt(), any(), any(), anyBoolean(),
+                any(Message.class));
+
+        DataNetwork dataNetworkIwlan = getDataNetworks().get(0);
+        assertThat(dataNetworkIwlan.getTransport()).isEqualTo(
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+    }
+
+    private void updateServiceStateForDatatNetwork(@Annotation.NetworkType int networkType,
+            @NetworkRegistrationInfo.RegistrationState int regState, DataNetwork dataNetwork) {
+        ServiceState ss = new ServiceState();
+        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setAccessNetworkTechnology(networkType)
+                .setRegistrationState(regState)
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setDataSpecificInfo(null)
+                .build());
+
+        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .build());
+
+        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setAccessNetworkTechnology(networkType)
+                .setRegistrationState(regState)
+                .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
+                .build());
+        ss.setDataRoamingFromRegistration(regState
+                == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+        doReturn(ss).when(mSST).getServiceState();
+        doReturn(ss).when(mPhone).getServiceState();
+
+        if (dataNetwork != null) {
+            dataNetwork.obtainMessage(9/*EVENT_SERVICE_STATE_CHANGED*/).sendToTarget();
+            processAllMessages();
+        }
+    }
+
+    @Test
     public void testHandoverDataNetworkSourceOosNoUnknownRule() throws Exception {
         testSetupImsDataNetwork();
         // Configured handover is allowed from OOS to 4G/5G/IWLAN.
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
index ed26b99..f3fed8c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
@@ -182,6 +182,28 @@
                             "CBS", 1).getBytes()))
             .build();
 
+    private final DataProfile m5gDataProfile = new DataProfile.Builder()
+            .setApnSetting(new ApnSetting.Builder()
+                    .setId(2163)
+                    .setOperatorNumeric("12345")
+                    .setEntryName("fake_apn")
+                    .setApnName(null /*empty name*/)
+                    .setUser("user")
+                    .setPassword("passwd")
+                    .setApnTypeBitmask(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_SUPL)
+                    .setProtocol(ApnSetting.PROTOCOL_IPV6)
+                    .setRoamingProtocol(ApnSetting.PROTOCOL_IP)
+                    .setCarrierEnabled(true)
+                    .setNetworkTypeBitmask((int) (TelephonyManager.NETWORK_TYPE_BITMASK_LTE
+                            | TelephonyManager.NETWORK_TYPE_BITMASK_NR))
+                    .setProfileId(1234)
+                    .setMaxConns(321)
+                    .setWaitTime(456)
+                    .setMaxConnsTime(789)
+                    .build())
+            .setTrafficDescriptor(new TrafficDescriptor(null, null))
+            .build();
+
     // Mocked classes
     private DataNetworkCallback mDataNetworkCallback;
     private DataCallSessionStats mDataCallSessionStats;
@@ -1777,6 +1799,28 @@
     }
 
     @Test
+    public void testPrivateNetwork() throws Exception {
+        NetworkRequestList networkRequestList = new NetworkRequestList();
+        networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .build(), mPhone));
+        mDataNetworkUT = new DataNetwork(mPhone, Looper.myLooper(), mDataServiceManagers,
+                m5gDataProfile, networkRequestList,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN, DataAllowedReason.NORMAL,
+                mDataNetworkCallback);
+        replaceInstance(DataNetwork.class, "mDataCallSessionStats",
+                mDataNetworkUT, mDataCallSessionStats);
+        processAllMessages();
+
+        verify(mMockedWwanDataServiceManager).setupDataCall(anyInt(),
+                eq(m5gDataProfile), eq(false), eq(false),
+                eq(DataService.REQUEST_REASON_NORMAL), nullable(LinkProperties.class),
+                eq(DataCallResponse.PDU_SESSION_ID_NOT_SET), nullable(NetworkSliceInfo.class),
+                // Verify matchAllRuleAllowed is flagged true
+                any(TrafficDescriptor.class), eq(true), any(Message.class));
+    }
+
+    @Test
     public void testLinkStatusUpdate() throws Exception {
         setupDataNetwork();
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
index e10c2a5..27271df 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
@@ -873,8 +873,10 @@
 
     @Test
     public void testSetInitialAttachDataProfileMultipleRequests() throws Exception {
+        // This test case only applies to legacy modem, see b/227579876
+        doReturn(false).when(mDataConfigManager).allowClearInitialAttachDataProfile();
+
         // Test: Modem Cleared IA, should always send IA to modem
-        // TODO(b/237444788): this case should be removed from U
         mDataProfileManagerUT.obtainMessage(3 /* EVENT_SIM_REFRESH */).sendToTarget();
         processAllMessages();
 
@@ -911,6 +913,14 @@
 
     @Test
     public void testSimRemoval() {
+        // This test case applies to the latest modem, see b/227579876.
+        doReturn(true).when(mDataConfigManager).allowClearInitialAttachDataProfile();
+
+        // SIM inserted
+        mDataProfileManagerUT.obtainMessage(3 /* EVENT_SIM_REFRESH */).sendToTarget();
+        processAllMessages();
+
+        // SIM removed
         Mockito.clearInvocations(mDataProfileManagerCallback);
         changeSimStateTo(TelephonyManager.SIM_STATE_ABSENT);
         mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget();
@@ -943,6 +953,58 @@
         dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
         assertThat(dataProfile).isEqualTo(null);
+
+        // Verify null as initial attached data profile is sent to modem
+        verify(mMockedWwanDataServiceManager).setInitialAttachApn(null, false, null);
+    }
+
+    @Test
+    public void testSimRemovalLegacy() {
+        // This test case only applies to legacy modem, see b/227579876, where null IA won't be
+        // updated to modem
+        doReturn(false).when(mDataConfigManager).allowClearInitialAttachDataProfile();
+
+        // SIM inserted
+        mDataProfileManagerUT.obtainMessage(3 /* EVENT_SIM_REFRESH */).sendToTarget();
+        processAllMessages();
+
+        // SIM removed
+        Mockito.clearInvocations(mDataProfileManagerCallback);
+        mSimInserted = false;
+        mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget();
+        processAllMessages();
+
+        verify(mDataProfileManagerCallback).onDataProfilesChanged();
+
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(
+                new NetworkRequest.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                        .build(), mPhone);
+        DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+        assertThat(dataProfile).isNull();
+
+        // expect default EIMS when SIM absent
+        tnr = new TelephonyNetworkRequest(
+                new NetworkRequest.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)
+                        .build(), mPhone);
+        dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+        assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo("sos");
+
+        // expect no default IMS when SIM absent
+        tnr = new TelephonyNetworkRequest(
+                new NetworkRequest.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
+                        .build(), mPhone);
+        dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+        assertThat(dataProfile).isEqualTo(null);
+
+        // Verify in legacy mode, null IA should NOT be sent to modem
+        verify(mMockedWwanDataServiceManager, Mockito.never())
+                .setInitialAttachApn(null, false, null);
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
index 84b3302..103b189 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
@@ -795,7 +795,7 @@
         // Verify scheduled via Alarm Manager
         ArgumentCaptor<PendingIntent> pendingIntentArgumentCaptor =
                 ArgumentCaptor.forClass(PendingIntent.class);
-        verify(mAlarmManager).setAndAllowWhileIdle(anyInt(), anyLong(),
+        verify(mAlarmManager).setExactAndAllowWhileIdle(anyInt(), anyLong(),
                 pendingIntentArgumentCaptor.capture());
 
         // Verify starts retry attempt after receiving intent
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java
index f7525c1..20cb73a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java
@@ -21,16 +21,22 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.os.Looper;
 import android.os.PersistableBundle;
+import android.telephony.TelephonyManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
@@ -131,4 +137,45 @@
         mDataSettingsManagerUT.setDefaultDataRoamingEnabled();
         assertFalse(mDataSettingsManagerUT.isDataRoamingEnabled());
     }
+
+    @Test
+    public void testUpdateDataEnabledAndNotifyOverride() throws Exception {
+        // Mock another DDS phone.
+        int ddsPhoneId = 1;
+        int ddsSubId = 2;
+        doReturn(ddsSubId).when(mSubscriptionManagerService).getDefaultDataSubId();
+        Phone phone2 = Mockito.mock(Phone.class);
+        doReturn(ddsPhoneId).when(phone2).getPhoneId();
+        doReturn(ddsSubId).when(phone2).getSubId();
+        doReturn(ddsPhoneId).when(mSubscriptionManagerService).getPhoneId(ddsSubId);
+        DataSettingsManager dataSettingsManager2 = Mockito.mock(DataSettingsManager.class);
+        doReturn(dataSettingsManager2).when(phone2).getDataSettingsManager();
+        mPhones = new Phone[] {mPhone, phone2};
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+        ArgumentCaptor<DataSettingsManagerCallback> callbackArgumentCaptor = ArgumentCaptor
+                .forClass(DataSettingsManagerCallback.class);
+
+        mDataSettingsManagerUT.sendEmptyMessage(11 /* EVENT_INITIALIZE */);
+        processAllMessages();
+
+        // Verify listening to user enabled status of other phones.
+        verify(dataSettingsManager2).registerCallback(callbackArgumentCaptor.capture());
+        DataSettingsManagerCallback callback = callbackArgumentCaptor.getValue();
+
+        // Mock the phone as nonDDS.
+        mDataSettingsManagerUT.setDataEnabled(TelephonyManager.DATA_ENABLED_REASON_USER, false, "");
+        processAllMessages();
+        clearInvocations(mPhone);
+
+        // Verify the override policy doesn't take effect because the DDS is user disabled.
+        mDataSettingsManagerUT.setMobileDataPolicy(
+                TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, true);
+        processAllMessages();
+        verify(mPhone, never()).notifyDataEnabled(anyBoolean(), anyInt());
+
+        // Verify the override takes effect upon DDS user enabled.
+        doReturn(true).when(phone2).isUserDataEnabled();
+        callback.onUserDataEnabledChanged(true, "callingPackage");
+        verify(mPhone).notifyDataEnabled(true, TelephonyManager.DATA_ENABLED_REASON_OVERRIDE);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java
index 35d3b92..22cdaae 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertNotNull;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
@@ -25,9 +26,16 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.content.Intent;
+import android.database.ContentObserver;
 import android.net.NetworkAgent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
 import android.telephony.Annotation.ValidationStatus;
 import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
+import android.test.mock.MockContentResolver;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -42,21 +50,53 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.List;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class DataStallRecoveryManagerTest extends TelephonyTest {
+    private FakeContentResolver mFakeContentResolver;
+
     // Mocked classes
     private DataStallRecoveryManagerCallback mDataStallRecoveryManagerCallback;
 
     private DataStallRecoveryManager mDataStallRecoveryManager;
 
+    /**
+     * The fake content resolver used to receive change event from global settings
+     * and notify observer of a change in content in DataStallRecoveryManager
+     */
+    private class FakeContentResolver extends MockContentResolver {
+        @Override
+        public void notifyChange(Uri uri, ContentObserver observer) {
+            super.notifyChange(uri, observer);
+            logd("onChanged(uri=" + uri + ")" + observer);
+            if (observer != null) {
+                observer.dispatchChange(false, uri);
+            } else {
+                mDataStallRecoveryManager.getContentObserver().dispatchChange(false, uri);
+            }
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
         logd("DataStallRecoveryManagerTest +Setup!");
         super.setUp(getClass().getSimpleName());
+        Field field = DataStallRecoveryManager.class.getDeclaredField("mPredictWaitingMillis");
+        field.setAccessible(true);
+
+        mFakeContentResolver = new FakeContentResolver();
+        doReturn(mFakeContentResolver).when(mContext).getContentResolver();
+        // Set the global settings for action enabled state and duration to
+        // the default test values.
+        Settings.Global.putString(mFakeContentResolver, Settings.Global.DSRM_DURATION_MILLIS,
+                "100,100,100,100,0");
+        Settings.Global.putString(mFakeContentResolver, Settings.Global.DSRM_ENABLED_ACTIONS,
+                "true,true,false,true,true");
+
         mDataStallRecoveryManagerCallback = mock(DataStallRecoveryManagerCallback.class);
         mCarrierConfigManager = mPhone.getContext().getSystemService(CarrierConfigManager.class);
         long[] dataStallRecoveryTimersArray = new long[] {100, 100, 100, 100};
@@ -81,11 +121,15 @@
                         mMockedWwanDataServiceManager,
                         mTestableLooper.getLooper(),
                         mDataStallRecoveryManagerCallback);
+
+        field.set(mDataStallRecoveryManager, 0L);
+
         logd("DataStallRecoveryManagerTest -Setup!");
     }
 
     @After
     public void tearDown() throws Exception {
+        mFakeContentResolver = null;
         mDataStallRecoveryManager = null;
         super.tearDown();
     }
@@ -93,22 +137,22 @@
     private void sendValidationStatusCallback(@ValidationStatus int status) {
         ArgumentCaptor<DataNetworkControllerCallback> dataNetworkControllerCallbackCaptor =
                 ArgumentCaptor.forClass(DataNetworkControllerCallback.class);
-        verify(mDataNetworkController)
+        verify(mDataNetworkController, times(2))
                 .registerDataNetworkControllerCallback(
                         dataNetworkControllerCallbackCaptor.capture());
         DataNetworkControllerCallback dataNetworkControllerCallback =
-                dataNetworkControllerCallbackCaptor.getValue();
+                dataNetworkControllerCallbackCaptor.getAllValues().get(0);
         dataNetworkControllerCallback.onInternetDataNetworkValidationStatusChanged(status);
     }
 
     private void sendOnInternetDataNetworkCallback(boolean isConnected) {
         ArgumentCaptor<DataNetworkControllerCallback> dataNetworkControllerCallbackCaptor =
                 ArgumentCaptor.forClass(DataNetworkControllerCallback.class);
-        verify(mDataNetworkController)
+        verify(mDataNetworkController, times(2))
                 .registerDataNetworkControllerCallback(
                         dataNetworkControllerCallbackCaptor.capture());
         DataNetworkControllerCallback dataNetworkControllerCallback =
-                dataNetworkControllerCallbackCaptor.getValue();
+                dataNetworkControllerCallbackCaptor.getAllValues().get(0);
 
         if (isConnected) {
             List<DataNetwork> dataprofile = new ArrayList<>();
@@ -363,4 +407,105 @@
         }
         assertThat(mDataStallRecoveryManager.mDataStallStartMs != 0).isTrue();
     }
+
+    /**
+     * Tests the DSRM process to send three intents for three action changes.
+     */
+    @Test
+    public void testSendDSRMData() throws Exception {
+        ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+
+        logd("Set phone status to normal status.");
+        sendOnInternetDataNetworkCallback(true);
+        doReturn(mSignalStrength).when(mPhone).getSignalStrength();
+        doReturn(PhoneConstants.State.IDLE).when(mPhone).getState();
+
+        // Set the expected behavior of the DataStallRecoveryManager.
+        logd("Start DSRM process, set action to 1");
+        mDataStallRecoveryManager.setRecoveryAction(1);
+        logd("Sending validation failed callback");
+        sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+        processAllFutureMessages();
+
+        logd("Verify that the DataStallRecoveryManager sends the expected intents.");
+        verify(mPhone.getContext(), times(3)).sendBroadcast(captorIntent.capture());
+        logd(captorIntent.getAllValues().toString());
+        for (int i = 0; i < captorIntent.getAllValues().size(); i++) {
+            Intent intent = captorIntent.getAllValues().get(i);
+            // Check and assert if intent is null
+            assertNotNull(intent);
+            // Check and assert if intent is not ACTION_DATA_STALL_DETECTED
+            assertThat(intent.getAction()).isEqualTo(
+                    TelephonyManager.ACTION_DATA_STALL_DETECTED);
+            // Get the extra data
+            Bundle bundle = (Bundle) intent.getExtra("EXTRA_DSRS_STATS_BUNDLE");
+            // Check and assert if bundle is null
+            assertNotNull(bundle);
+            // Dump bundle data
+            logd(bundle.toString());
+            int size = bundle.size();
+            logd("bundle size is " + size);
+            // Check if bundle size is 19
+            assertThat(size).isEqualTo(19);
+        }
+    }
+
+    /**
+     * Tests update action enable state and duration from global settings.
+     */
+    @Test
+    public void testUpdateGlobalSettings() throws Exception {
+        Field field = DataStallRecoveryManager.class.getDeclaredField("mPredictWaitingMillis");
+        field.setAccessible(true);
+
+        // Set duration to 10000/20000/30000/40000
+        Settings.Global.putString(
+                mFakeContentResolver, Settings.Global.DSRM_DURATION_MILLIS,
+                "10000,20000,30000,40000,0");
+        // Send onChange event with Settings.Global.DSRM_DURATION_MILLIS to fake ContentResolver
+        mFakeContentResolver.notifyChange(
+                Settings.Global.getUriFor(Settings.Global.DSRM_DURATION_MILLIS), null);
+        processAllFutureMessages();
+        // Verify that the durations are correct values.
+        assertThat(mDataStallRecoveryManager.getDataStallRecoveryDelayMillis(0)).isEqualTo(10000L);
+        assertThat(mDataStallRecoveryManager.getDataStallRecoveryDelayMillis(1)).isEqualTo(20000L);
+        assertThat(mDataStallRecoveryManager.getDataStallRecoveryDelayMillis(2)).isEqualTo(30000L);
+        assertThat(mDataStallRecoveryManager.getDataStallRecoveryDelayMillis(3)).isEqualTo(40000L);
+
+        // Set action enable state to true/false/false/false/true
+        Settings.Global.putString(
+                mFakeContentResolver, Settings.Global.DSRM_ENABLED_ACTIONS,
+                "true,false,false,false,true");
+        // Send onChange event with Settings.Global.DSRM_ENABLED_ACTIONS to fake ContentResolver
+        mFakeContentResolver.notifyChange(
+                Settings.Global.getUriFor(Settings.Global.DSRM_ENABLED_ACTIONS), null);
+        processAllFutureMessages();
+        // Verify that the action enable state are correct values.
+        assertThat(mDataStallRecoveryManager.shouldSkipRecoveryAction(0)).isEqualTo(false);
+        assertThat(mDataStallRecoveryManager.shouldSkipRecoveryAction(1)).isEqualTo(true);
+        assertThat(mDataStallRecoveryManager.shouldSkipRecoveryAction(2)).isEqualTo(true);
+        assertThat(mDataStallRecoveryManager.shouldSkipRecoveryAction(3)).isEqualTo(true);
+        assertThat(mDataStallRecoveryManager.shouldSkipRecoveryAction(4)).isEqualTo(false);
+        // Check the predict waiting millis
+        assertThat(field.get(mDataStallRecoveryManager)).isEqualTo(1000L);
+        // Test predict waiting millis to rollback to 0 if there is no global duration and action
+        // Set duration to empty
+        Settings.Global.putString(
+                mFakeContentResolver, Settings.Global.DSRM_DURATION_MILLIS,
+                "");
+        // Send onChange event with Settings.Global.DSRM_DURATION_MILLIS to fake ContentResolver
+        mFakeContentResolver.notifyChange(
+                Settings.Global.getUriFor(Settings.Global.DSRM_DURATION_MILLIS), null);
+        processAllFutureMessages();
+        // Set action to empty
+        Settings.Global.putString(
+                mFakeContentResolver, Settings.Global.DSRM_ENABLED_ACTIONS,
+                "");
+        // Send onChange event with Settings.Global.DSRM_ENABLED_ACTIONS to fake ContentResolver
+        mFakeContentResolver.notifyChange(
+                Settings.Global.getUriFor(Settings.Global.DSRM_ENABLED_ACTIONS), null);
+        processAllFutureMessages();
+        // Check if predict waiting millis is 0
+        assertThat(field.get(mDataStallRecoveryManager)).isEqualTo(0L);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
index 0eba3fe..c215483 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
@@ -26,9 +26,8 @@
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM;
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
-
+import static com.android.internal.telephony.data.AutoDataSwitchController.EVALUATION_REASON_VOICE_CALL_END;
 import static com.android.internal.telephony.data.PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -65,6 +64,8 @@
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyDisplayInfo;
+import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -118,11 +119,13 @@
     private GsmCdmaCall mInactiveCall;
     private GsmCdmaCall mDialCall;
     private GsmCdmaCall mIncomingCall;
+    private GsmCdmaCall mAlertingCall;
     private ISetOpportunisticDataCallback mSetOpptDataCallback1;
     private ISetOpportunisticDataCallback mSetOpptDataCallback2;
     PhoneSwitcher.ImsRegTechProvider mMockImsRegTechProvider;
     private SubscriptionInfo mSubscriptionInfo;
     private ISub mMockedIsub;
+    private AutoDataSwitchController mAutoDataSwitchController;
 
     private PhoneSwitcher mPhoneSwitcherUT;
     private SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener;
@@ -137,6 +140,10 @@
     private int mActiveModemCount = 2;
     private int mSupportedModemCount = 2;
     private int mMaxDataAttachModemCount = 1;
+    private AutoDataSwitchController.AutoDataSwitchControllerCallback mAutoDataSwitchCallback;
+    private TelephonyDisplayInfo mTelephonyDisplayInfo = new TelephonyDisplayInfo(
+            TelephonyManager.NETWORK_TYPE_NR,
+            TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false);
 
     @Before
     public void setUp() throws Exception {
@@ -153,11 +160,13 @@
         mInactiveCall = mock(GsmCdmaCall.class);
         mDialCall = mock(GsmCdmaCall.class);
         mIncomingCall = mock(GsmCdmaCall.class);
+        mAlertingCall = mock(GsmCdmaCall.class);
         mSetOpptDataCallback1 = mock(ISetOpportunisticDataCallback.class);
         mSetOpptDataCallback2 = mock(ISetOpportunisticDataCallback.class);
         mMockImsRegTechProvider = mock(PhoneSwitcher.ImsRegTechProvider.class);
         mSubscriptionInfo = mock(SubscriptionInfo.class);
         mMockedIsub = mock(ISub.class);
+        mAutoDataSwitchController = mock(AutoDataSwitchController.class);
 
         PhoneCapability phoneCapability = new PhoneCapability(1, 1, null, false, new int[0]);
         doReturn(phoneCapability).when(mPhoneConfigurationManager).getCurrentPhoneCapability();
@@ -167,6 +176,7 @@
         doReturn(Call.State.HOLDING).when(mHoldingCall).getState();
         doReturn(Call.State.DIALING).when(mDialCall).getState();
         doReturn(Call.State.INCOMING).when(mIncomingCall).getState();
+        doReturn(Call.State.ALERTING).when(mAlertingCall).getState();
 
         doReturn(true).when(mInactiveCall).isIdle();
         doReturn(false).when(mActiveCall).isIdle();
@@ -179,6 +189,8 @@
         doReturn(mMockedIsub).when(mIBinder).queryLocalInterface(anyString());
         doReturn(mPhone).when(mPhone).getImsPhone();
         mServiceManagerMockedServices.put("isub", mIBinder);
+
+        doReturn(mTelephonyDisplayInfo).when(mDisplayInfoController).getTelephonyDisplayInfo();
     }
 
     @After
@@ -187,6 +199,7 @@
         mSubChangedListener = null;
         mConnectivityManager = null;
         mNetworkProviderMessenger = null;
+        mTelephonyDisplayInfo = null;
         super.tearDown();
     }
 
@@ -374,194 +387,7 @@
     }
 
     /** Test Data Auto Switch **/
-
-    /**
-     * Trigger conditions
-     * 1. service state changes
-     * 2. data setting changes
-     *      - user toggle data
-     *      - user toggle auto switch feature
-     * 3. default network changes
-     *      - current network lost
-     *      - network become active on non-cellular network
-     * 4. subscription changes
-     *      - slot/sub mapping changes
-     */
     @Test
-    @SmallTest
-    public void testAutoDataSwitchCancelScenario_onPrimary() throws Exception {
-        initialize();
-        // Phone 0 has sub 1, phone 1 has sub 2.
-        // Sub 1 is default data sub.
-        setSlotIndexToSubId(0, 1);
-        setSlotIndexToSubId(1, 2);
-        setDefaultDataSubId(1);
-
-        // 0. When all conditions met
-        prepareIdealAutoSwitchCondition();
-        processAllFutureMessages();
-
-        // Verify attempting to switch
-        verify(mCellularNetworkValidator).validate(eq(2), anyLong(), eq(false),
-                eq(mPhoneSwitcherUT.mValidationCallback));
-        doReturn(true).when(mCellularNetworkValidator).isValidating();
-
-        // 1. Service state becomes not ideal - primary is available again
-        clearInvocations(mCellularNetworkValidator);
-        serviceStateChanged(0, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
-        processAllFutureMessages();
-
-        verify(mCellularNetworkValidator).stopValidation();
-
-        // 2.1 User data disabled on primary SIM
-        prepareIdealAutoSwitchCondition();
-        processAllFutureMessages();
-        clearInvocations(mCellularNetworkValidator);
-        doReturn(false).when(mPhone).isUserDataEnabled();
-        mDataSettingsManagerCallbacks.get(0).onDataEnabledChanged(false, 123 , "");
-        processAllFutureMessages();
-
-        verify(mCellularNetworkValidator).stopValidation();
-
-        // 2.2 Auto switch feature is disabled
-        prepareIdealAutoSwitchCondition();
-        processAllFutureMessages();
-        clearInvocations(mCellularNetworkValidator);
-        doReturn(false).when(mPhone2).isDataAllowed();
-        mDataSettingsManagerCallbacks.get(1).onDataEnabledChanged(false, 123 , "");
-        processAllFutureMessages();
-
-        verify(mCellularNetworkValidator).stopValidation();
-
-        // 3.1 No default network
-        prepareIdealAutoSwitchCondition();
-        processAllFutureMessages();
-        clearInvocations(mCellularNetworkValidator);
-        doReturn(new NetworkCapabilities()
-                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI))
-                .when(mConnectivityManager).getNetworkCapabilities(any());
-        mPhoneSwitcherUT.sendEmptyMessage(EVENT_EVALUATE_AUTO_SWITCH);
-        processAllFutureMessages();
-
-        verify(mCellularNetworkValidator).stopValidation();
-    }
-
-    public void testAutoSwitchToSecondarySucceed() {
-        prepareIdealAutoSwitchCondition();
-        processAllFutureMessages();
-        mPhoneSwitcherUT.mValidationCallback.onValidationDone(true, 2);
-        processAllMessages();
-        // Confirm auto switched to secondary sub Id 1/phone 0
-        assertEquals(2, mPhoneSwitcherUT.getActiveDataSubId());
-    }
-
-    @Test
-    @SmallTest
-    public void testAutoDataSwitch_switchBackToPrimary() throws Exception {
-        initialize();
-        // Phone 0 has sub 1, phone 1 has sub 2.
-        // Sub 1 is default data sub.
-        setSlotIndexToSubId(0, 1);
-        setSlotIndexToSubId(1, 2);
-        setDefaultDataSubId(1);
-
-        testAutoSwitchToSecondarySucceed();
-        // 1.1 service state changes - primary becomes available, need validation pass to switch
-        serviceStateChanged(0, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
-        processAllFutureMessages();
-        verify(mCellularNetworkValidator).validate(eq(1), anyLong(), eq(false),
-                eq(mPhoneSwitcherUT.mValidationCallback));
-        mPhoneSwitcherUT.mValidationCallback.onValidationDone(false, 1);
-        processAllMessages();
-
-        assertEquals(2, mPhoneSwitcherUT.getActiveDataSubId()); // since validation failed
-
-        serviceStateChanged(0, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
-        processAllFutureMessages();
-        mPhoneSwitcherUT.mValidationCallback.onValidationDone(true, 1);
-        processAllMessages();
-
-        assertEquals(1, mPhoneSwitcherUT.getActiveDataSubId()); // since validation passed
-
-        testAutoSwitchToSecondarySucceed();
-        // 1.2 service state changes - secondary becomes unavailable, NO need validation
-        // The later validation requirement overrides the previous
-        serviceStateChanged(0, NetworkRegistrationInfo.REGISTRATION_STATE_HOME/*need validate*/);
-        serviceStateChanged(1, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING/*no need*/);
-        processAllFutureMessages();
-        mPhoneSwitcherUT.mValidationCallback.onValidationDone(false, 1);
-        processAllMessages();
-
-        assertEquals(1, mPhoneSwitcherUT.getActiveDataSubId()); // since no need validation
-
-        testAutoSwitchToSecondarySucceed();
-        // 2.1 User data disabled on primary SIM
-        clearInvocations(mCellularNetworkValidator);
-        doReturn(false).when(mPhone).isUserDataEnabled();
-        mDataSettingsManagerCallbacks.get(0).onDataEnabledChanged(false, 123 , "");
-        processAllFutureMessages();
-
-        assertEquals(1, mPhoneSwitcherUT.getActiveDataSubId()); // since no need validation
-        verify(mCellularNetworkValidator, never()).validate(eq(1), anyLong(), eq(false),
-                eq(mPhoneSwitcherUT.mValidationCallback));
-
-        testAutoSwitchToSecondarySucceed();
-        // 2.2 Auto switch feature is disabled
-        clearInvocations(mCellularNetworkValidator);
-        doReturn(false).when(mPhone2).isDataAllowed();
-        mDataSettingsManagerCallbacks.get(0).onDataEnabledChanged(false, 123 , "");
-        processAllFutureMessages();
-
-        assertEquals(1, mPhoneSwitcherUT.getActiveDataSubId()); // since no need validation
-        verify(mCellularNetworkValidator, never()).validate(eq(1), anyLong(), eq(false),
-                eq(mPhoneSwitcherUT.mValidationCallback));
-
-        testAutoSwitchToSecondarySucceed();
-        // 3.1 Default network is active on non-cellular transport
-        clearInvocations(mCellularNetworkValidator);
-        doReturn(new NetworkCapabilities()
-                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI))
-                .when(mConnectivityManager).getNetworkCapabilities(any());
-        mPhoneSwitcherUT.sendEmptyMessage(EVENT_EVALUATE_AUTO_SWITCH);
-        processAllFutureMessages();
-        mPhoneSwitcherUT.mValidationCallback.onValidationDone(false, 1);
-        processAllMessages();
-
-        assertEquals(1, mPhoneSwitcherUT.getActiveDataSubId()); // since no need validation
-    }
-
-    @Test
-    @SmallTest
-    public void testAutoDataSwitchCancel_onSecondary() throws Exception {
-        initialize();
-        // Phone 0 has sub 1, phone 1 has sub 2.
-        // Sub 1 is default data sub.
-        setSlotIndexToSubId(0, 1);
-        setSlotIndexToSubId(1, 2);
-        setDefaultDataSubId(1);
-
-        testAutoSwitchToSecondarySucceed();
-        clearInvocations(mCellularNetworkValidator);
-        // attempts the switch back due to secondary becomes ROAMING
-        serviceStateChanged(1, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
-        processAllFutureMessages();
-
-        verify(mCellularNetworkValidator).validate(eq(1), anyLong(), eq(false),
-                eq(mPhoneSwitcherUT.mValidationCallback));
-        doReturn(true).when(mCellularNetworkValidator).isValidating();
-
-        // cancel the switch back attempt due to secondary back to HOME
-        serviceStateChanged(1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
-        processAllMessages();
-        mPhoneSwitcherUT.mValidationCallback.onValidationDone(true, 1);
-        processAllMessages();
-
-        assertEquals(2, mPhoneSwitcherUT.getActiveDataSubId());
-        verify(mCellularNetworkValidator).stopValidation();
-    }
-
-    @Test
-    @SmallTest
     public void testAutoDataSwitch_retry() throws Exception {
         initialize();
         // Phone 0 has sub 1, phone 1 has sub 2.
@@ -570,30 +396,28 @@
         setSlotIndexToSubId(1, 2);
         setDefaultDataSubId(1);
 
-        prepareIdealAutoSwitchCondition();
+        mAutoDataSwitchCallback.onRequireValidation(1/*Phone2*/, true);
         processAllFutureMessages();
 
-        // Verify attempting to switch
+        // Mock validation failed, expect retry attempt
         verify(mCellularNetworkValidator).validate(eq(2), anyLong(), eq(false),
                 eq(mPhoneSwitcherUT.mValidationCallback));
-        mPhoneSwitcherUT.mValidationCallback.onValidationDone(false, 2);
+        mPhoneSwitcherUT.mValidationCallback.onValidationDone(false, 2/*Phone2*/);
         processAllMessages();
 
-        assertTrue(mPhoneSwitcherUT.hasMessages(EVENT_EVALUATE_AUTO_SWITCH));
+        verify(mAutoDataSwitchController).evaluateRetryOnValidationFailed();
 
+        // Test clear failed count upon switch succeeded.
+        mAutoDataSwitchCallback.onRequireValidation(1/*Phone2*/, true);
         processAllFutureMessages();
-        mPhoneSwitcherUT.mValidationCallback.onValidationDone(true, 2);
-        processAllFutureMessages();
+        mPhoneSwitcherUT.mValidationCallback.onValidationDone(true, 2/*Phone2*/);
+        processAllMessages();
 
-        assertEquals(2, mPhoneSwitcherUT.getActiveDataSubId());
+        verify(mAutoDataSwitchController).resetFailedCount();
     }
 
     @Test
-    @SmallTest
     public void testAutoDataSwitch_setNotification() throws Exception {
-        SubscriptionInfo mockedInfo = mock(SubscriptionInfo.class);
-        doReturn(false).when(mockedInfo).isOpportunistic();
-        doReturn(mockedInfo).when(mSubscriptionManagerService).getSubscriptionInfo(anyInt());
         initialize();
         // Phone 0 has sub 1, phone 1 has sub 2.
         // Sub 1 is default data sub.
@@ -601,60 +425,50 @@
         setSlotIndexToSubId(1, 2);
         setDefaultDataSubId(1);
 
-        testAutoSwitchToSecondarySucceed();
-        clearInvocations(mSubscriptionManagerService);
-        Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, new AsyncResult(1, null,  null))
-                .sendToTarget();
+        // Verify no notification check if switch failed.
+        Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, new AsyncResult(1/*phoneId*/,
+                null, new Throwable())).sendToTarget();
         processAllMessages();
-        verify(mSubscriptionManagerService).getSubscriptionInfo(2);
-        // switch back to primary
-        clearInvocations(mSubscriptionManagerService);
-        Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, new AsyncResult(0, null,  null))
-                .sendToTarget();
-        processAllMessages();
-        verify(mSubscriptionManagerService, never()).getSubscriptionInfo(1);
+        verify(mAutoDataSwitchController, never()).displayAutoDataSwitchNotification(
+                anyInt(), anyBoolean());
 
-        Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, new AsyncResult(1, null,  null))
-                .sendToTarget();
+        // Verify for switch not due to auto
+        Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, new AsyncResult(1/*phoneId*/,
+                null, null)).sendToTarget();
         processAllMessages();
-        verify(mSubscriptionManagerService, never()).getSubscriptionInfo(2);
+        verify(mAutoDataSwitchController).displayAutoDataSwitchNotification(1/*phoneId*/, false);
+
+        // Verify for switch due to auto
+        mAutoDataSwitchCallback.onRequireValidation(1/*Phone2*/, false);
+        processAllMessages();
+        mPhoneSwitcherUT.mValidationCallback.onValidationDone(true, 2/*Phone2*/);
+        Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, new AsyncResult(1/*phoneId*/,
+                null, null)).sendToTarget();
+        processAllMessages();
+
+        verify(mAutoDataSwitchController).displayAutoDataSwitchNotification(1/*phoneId*/, true);
     }
 
     @Test
     @SmallTest
     public void testAutoDataSwitch_exemptPingTest() throws Exception {
         initialize();
-        // Change resource overlay
-        doReturn(false).when(mDataConfigManager).isPingTestBeforeAutoDataSwitchRequired();
-        mPhoneSwitcherUT = new PhoneSwitcher(mMaxDataAttachModemCount, mContext, Looper.myLooper());
-        processAllMessages();
 
         // Phone 0 has sub 1, phone 1 has sub 2.
         // Sub 1 is default data sub.
         setSlotIndexToSubId(0, 1);
         setSlotIndexToSubId(1, 2);
-        setDefaultDataSubId(1);
 
-        //1. Attempting to switch to nDDS, switch even if validation failed
-        prepareIdealAutoSwitchCondition();
+        //Attempting to switch to nDDS, switch even if validation failed
+        mAutoDataSwitchCallback.onRequireValidation(1/*Phone2*/, false);
         processAllFutureMessages();
 
         verify(mCellularNetworkValidator).validate(eq(2), anyLong(), eq(false),
                 eq(mPhoneSwitcherUT.mValidationCallback));
-        mPhoneSwitcherUT.mValidationCallback.onValidationDone(false, 2);
+        mPhoneSwitcherUT.mValidationCallback.onValidationDone(false, 2/*Phone2*/);
         processAllMessages();
 
         assertEquals(2, mPhoneSwitcherUT.getActiveDataSubId()); // switch succeeds
-
-        //2. Attempting to switch back to DDS, switch even if validation failed
-        serviceStateChanged(0, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
-        processAllFutureMessages();
-        verify(mCellularNetworkValidator).validate(eq(1), anyLong(), eq(false),
-                eq(mPhoneSwitcherUT.mValidationCallback));
-        mPhoneSwitcherUT.mValidationCallback.onValidationDone(false, 1);
-        processAllMessages();
-
-        assertEquals(1, mPhoneSwitcherUT.getActiveDataSubId()); // switch succeeds
     }
 
     /**
@@ -1096,13 +910,19 @@
         // Phone 0 should be the default data phoneId.
         assertEquals(0, mPhoneSwitcherUT.getPreferredDataPhoneId());
 
-        // Phone2 has active IMS call on LTE. And data of DEFAULT apn is enabled. This should
-        // trigger data switch.
+        // Dialing shouldn't trigger switch because we give modem time to deal with the dialing call
+        // first. Phone2 has active IMS call on LTE. And data of DEFAULT apn is enabled.
         doReturn(mImsPhone).when(mPhone2).getImsPhone();
         doReturn(true).when(mPhone2).isDataAllowed();
         mockImsRegTech(1, REGISTRATION_TECH_LTE);
         notifyPhoneAsInDial(mImsPhone);
 
+        // Phone1 should remain as the preferred data phone
+        assertEquals(0, mPhoneSwitcherUT.getPreferredDataPhoneId());
+
+        // Dialing -> Alert, should trigger phone switch
+        notifyPhoneAsAlerting(mImsPhone);
+
         // Phone2 should be preferred data phone
         assertEquals(1, mPhoneSwitcherUT.getPreferredDataPhoneId());
     }
@@ -1200,7 +1020,6 @@
     }
 
     @Test
-    @SmallTest
     public void testNonDefaultDataPhoneInCall() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
@@ -1250,11 +1069,9 @@
 
         // Phone(DDS) call ended.
         // Honor auto data switch's suggestion: if DDS is OOS, auto switch to Phone2(nDDS).
-        serviceStateChanged(1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
-        serviceStateChanged(0, NetworkRegistrationInfo
-                .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING);
-        doReturn(null).when(mConnectivityManager).getNetworkCapabilities(any());
         notifyPhoneAsInactive(mPhone);
+        verify(mAutoDataSwitchController).evaluateAutoDataSwitch(EVALUATION_REASON_VOICE_CALL_END);
+        mAutoDataSwitchCallback.onRequireValidation(1 /*Phone2*/, true);
 
         // verify immediately switch back to DDS upon call ends
         verify(mMockRadioConfig).setPreferredDataModem(eq(0), any());
@@ -1930,6 +1747,12 @@
         processAllMessages();
     }
 
+    private void notifyPhoneAsAlerting(Phone phone) {
+        doReturn(mAlertingCall).when(phone).getForegroundCall();
+        mPhoneSwitcherUT.sendEmptyMessage(EVENT_PRECISE_CALL_STATE_CHANGED);
+        processAllMessages();
+    }
+
     private void notifyPhoneAsInIncomingCall(Phone phone) {
         doReturn(mIncomingCall).when(phone).getForegroundCall();
         mPhoneSwitcherUT.sendEmptyMessage(EVENT_PRECISE_CALL_STATE_CHANGED);
@@ -2042,6 +1865,13 @@
                 (Map<Integer, DataSettingsManager.DataSettingsManagerCallback>)
                         field.get(mPhoneSwitcherUT);
 
+        field = PhoneSwitcher.class.getDeclaredField("mAutoDataSwitchCallback");
+        field.setAccessible(true);
+        mAutoDataSwitchCallback = (AutoDataSwitchController.AutoDataSwitchControllerCallback)
+                field.get(mPhoneSwitcherUT);
+
+        replaceInstance(PhoneSwitcher.class, "mAutoDataSwitchController", mPhoneSwitcherUT,
+                mAutoDataSwitchController);
         processAllMessages();
 
         verify(mTelephonyRegistryManager).addOnSubscriptionsChangedListener(any(), any());
@@ -2058,6 +1888,9 @@
         doReturn(true).when(mPhone2).isUserDataEnabled();
         doReturn(mDataSettingsManager2).when(mPhone2).getDataSettingsManager();
         doReturn(mSST2).when(mPhone2).getServiceStateTracker();
+        doReturn(mDisplayInfoController).when(mPhone2).getDisplayInfoController();
+        doReturn(mSignalStrengthController).when(mPhone2).getSignalStrengthController();
+        doReturn(mSignalStrength).when(mPhone2).getSignalStrength();
         for (int i = 0; i < supportedModemCount; i++) {
             mSlotIndexToSubId[i] = new int[1];
             mSlotIndexToSubId[i][0] = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -2192,12 +2025,9 @@
     private void setDefaultDataSubId(int defaultDataSub) {
         mDefaultDataSub = defaultDataSub;
         doReturn(mDefaultDataSub).when(mSubscriptionManagerService).getDefaultDataSubId();
-        if (defaultDataSub == 1) {
-            doReturn(true).when(mPhone).isUserDataEnabled();
-            doReturn(false).when(mPhone2).isUserDataEnabled();
-        } else {
-            doReturn(false).when(mPhone).isUserDataEnabled();
-            doReturn(true).when(mPhone2).isUserDataEnabled();
+        for (Phone phone : mPhones) {
+            doAnswer(invocation -> phone.getSubId() == mDefaultDataSub)
+                    .when(phone).isUserDataEnabled();
         }
         sendDefaultDataSubChanged();
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
index b74c8af..cd9d9ad 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
@@ -47,6 +47,7 @@
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.nullable;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
@@ -1049,6 +1050,67 @@
         mContextFixture.addCallingOrSelfPermission("");
     }
 
+    @Test
+    @SmallTest
+    public void testSetPhoneNumberForSourceIgnoreGlobalPhoneNumberFormat() {
+        // In reality the method under test runs in phone process so has MODIFY_PHONE_STATE
+        mContextFixture.addCallingOrSelfPermission(MODIFY_PHONE_STATE);
+        int subId = 1;
+        doReturn(subId).when(mPhone).getSubId();
+        doReturn(new SubscriptionInfoInternal.Builder().setId(subId).setSimSlotIndex(0)
+                .setCountryIso("gb").build()).when(mSubscriptionManagerService)
+                .getSubscriptionInfoInternal(subId);
+        // Set carrier config to ignore global phone number format
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean(
+                CarrierConfigManager.Ims.KEY_ALLOW_NON_GLOBAL_PHONE_NUMBER_FORMAT_BOOL, true);
+        doReturn(bundle).when(mCarrierConfigManager).getConfigForSubId(eq(subId),
+                eq(CarrierConfigManager.Ims.KEY_ALLOW_NON_GLOBAL_PHONE_NUMBER_FORMAT_BOOL));
+
+        // 1. Two non-global phone number; 1st is set.
+        Uri[] associatedUris = new Uri[] {
+                Uri.parse("sip:01012345678@lte-uplus.co.kr"),
+                Uri.parse("tel:01012345678")
+        };
+        mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris);
+
+        verify(mSubscriptionManagerService).setNumberFromIms(subId, "01012345678");
+
+        // 2. 1st non-global phone number and 2nd global number; 2nd is set.
+        associatedUris = new Uri[] {
+                Uri.parse("sip:01012345678@lte-uplus.co.kr"),
+                Uri.parse("tel:+821012345678")
+        };
+        mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris);
+
+        verify(mSubscriptionManagerService).setNumberFromIms(subId, "+821012345678");
+
+        // 3. 1st sip-uri is not phone number and 2nd valid: 2nd is set.
+        associatedUris = new Uri[] {
+                Uri.parse("sip:john.doe@ims.x.com"),
+                Uri.parse("sip:01022223333@lte-uplus.co.kr"),
+                Uri.parse("tel:01022223333")
+        };
+        mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris);
+
+        verify(mSubscriptionManagerService).setNumberFromIms(subId, "01022223333");
+
+        clearInvocations(mSubscriptionManagerService);
+
+        // 4. Invalid phone number : not set.
+        associatedUris = new Uri[] {
+                Uri.parse("sip:john.doe@ims.x.com"),
+                Uri.parse("sip:hone3333@lte-uplus.co.kr"),
+                Uri.parse("tel:abcd1234555")
+        };
+        mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris);
+
+        verify(mSubscriptionManagerService, never()).setNumberFromIms(anyInt(), anyString());
+
+        // Clean up
+        mContextFixture.addCallingOrSelfPermission("");
+    }
+
     /**
      * Verifies that valid radio technology is passed to RIL
      * when IMS registration state changes to registered.
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java
index 7b66a52..f186f98 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java
@@ -30,6 +30,7 @@
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
@@ -152,6 +153,7 @@
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -324,6 +326,7 @@
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         state = captor.getAllValues().get(1);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
@@ -336,6 +339,7 @@
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -373,6 +377,7 @@
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         state = captor.getAllValues().get(1);
         assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, state.dataRat);
@@ -385,6 +390,7 @@
         assertEquals(200L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -416,6 +422,7 @@
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         state = captor.getAllValues().get(1);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
@@ -457,6 +464,7 @@
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -495,6 +503,7 @@
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         state = serviceStateCaptor.getAllValues().get(1);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
@@ -507,6 +516,7 @@
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         CellularDataServiceSwitch serviceSwitch = serviceSwitchCaptor.getAllValues().get(0);
         assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, serviceSwitch.ratFrom);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, serviceSwitch.ratTo);
@@ -548,6 +558,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         state = captor.getAllValues().get(1);
         assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
@@ -559,6 +570,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -588,6 +600,7 @@
         assertEquals(0L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -629,6 +642,7 @@
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         state = captor.getAllValues().get(1);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
@@ -641,6 +655,7 @@
         assertEquals(200L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         state = captor.getAllValues().get(2);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
@@ -653,6 +668,7 @@
         assertEquals(400L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         state = captor.getAllValues().get(3);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
@@ -665,6 +681,7 @@
         assertEquals(800L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -710,6 +727,7 @@
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         state = captor.getAllValues().get(1);
         assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, state.dataRat);
@@ -722,6 +740,7 @@
         assertEquals(5000L, state.totalTimeMillis);
         assertEquals(true, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         state = captor.getAllValues().get(2);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
@@ -734,6 +753,7 @@
         assertEquals(200L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -750,11 +770,31 @@
         mockWwanPsRat(TelephonyManager.NETWORK_TYPE_UMTS);
         mockWwanCsRat(TelephonyManager.NETWORK_TYPE_UMTS);
         doReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN).when(mImsStats).getImsVoiceRadioTech();
-        doReturn(ServiceState.ROAMING_TYPE_INTERNATIONAL).when(mServiceState).getVoiceRoamingType();
+        NetworkRegistrationInfo voiceNri = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UMTS)
+                // This sets mNetworkRegistrationState
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING)
+                .build();
+        voiceNri.setRoamingType(ServiceState.ROAMING_TYPE_INTERNATIONAL);
+        doReturn(voiceNri)
+                .when(mServiceState)
+                .getNetworkRegistrationInfo(
+                        NetworkRegistrationInfo.DOMAIN_CS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
         mServiceStateStats.onServiceStateChanged(mServiceState);
         mServiceStateStats.incTimeMillis(200L);
         // Voice and data roaming
-        doReturn(ServiceState.ROAMING_TYPE_INTERNATIONAL).when(mServiceState).getDataRoamingType();
+        NetworkRegistrationInfo dataNri = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UMTS)
+                // This sets mNetworkRegistrationState
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING)
+                .build();
+        dataNri.setRoamingType(ServiceState.ROAMING_TYPE_INTERNATIONAL);
+        doReturn(dataNri)
+                .when(mServiceState)
+                .getNetworkRegistrationInfo(
+                        NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
         mServiceStateStats.onServiceStateChanged(mServiceState);
         mServiceStateStats.incTimeMillis(400L);
 
@@ -779,6 +819,7 @@
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         state = serviceStateCaptor.getAllValues().get(1);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.dataRat);
@@ -791,6 +832,7 @@
         assertEquals(200L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         state = serviceStateCaptor.getAllValues().get(2);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.dataRat);
@@ -803,6 +845,7 @@
         assertEquals(400L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         CellularDataServiceSwitch serviceSwitch = serviceSwitchCaptor.getAllValues().get(0);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, serviceSwitch.ratFrom);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, serviceSwitch.ratTo);
@@ -817,6 +860,76 @@
 
     @Test
     @SmallTest
+    public void onServiceStateChanged_roamingWithOverride() throws Exception {
+        // Using default service state for LTE
+
+        mServiceStateStats.onServiceStateChanged(mServiceState);
+        mServiceStateStats.incTimeMillis(100L);
+        // Voice roaming
+        doReturn(TelephonyManager.NETWORK_TYPE_UMTS).when(mServiceState).getVoiceNetworkType();
+        doReturn(TelephonyManager.NETWORK_TYPE_UMTS).when(mServiceState).getDataNetworkType();
+        NetworkRegistrationInfo roamingNri = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UMTS)
+                // This sets mNetworkRegistrationState
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING)
+                .build();
+        roamingNri.setRoamingType(ServiceState.ROAMING_TYPE_NOT_ROAMING);
+        doReturn(roamingNri).when(mServiceState)
+                .getNetworkRegistrationInfo(
+                        anyInt(), eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN));
+        doReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN).when(mImsStats).getImsVoiceRadioTech();
+        mServiceStateStats.onServiceStateChanged(mServiceState);
+        mServiceStateStats.incTimeMillis(400L);
+
+        // There should be 2 service states and 1 data service switch (LTE to UMTS)
+        mServiceStateStats.conclude();
+        ArgumentCaptor<CellularServiceState> serviceStateCaptor =
+                ArgumentCaptor.forClass(CellularServiceState.class);
+        ArgumentCaptor<CellularDataServiceSwitch> serviceSwitchCaptor =
+                ArgumentCaptor.forClass(CellularDataServiceSwitch.class);
+        verify(mPersistAtomsStorage, times(2))
+                .addCellularServiceStateAndCellularDataServiceSwitch(
+                        serviceStateCaptor.capture(), serviceSwitchCaptor.capture());
+        CellularServiceState state = serviceStateCaptor.getAllValues().get(0);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
+        assertEquals(ServiceState.ROAMING_TYPE_NOT_ROAMING, state.voiceRoamingType);
+        assertEquals(ServiceState.ROAMING_TYPE_NOT_ROAMING, state.dataRoamingType);
+        assertFalse(state.isEndc);
+        assertEquals(0, state.simSlotIndex);
+        assertFalse(state.isMultiSim);
+        assertEquals(CARRIER1_ID, state.carrierId);
+        assertEquals(100L, state.totalTimeMillis);
+        assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
+        state = serviceStateCaptor.getAllValues().get(1);
+        assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.voiceRat);
+        assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.dataRat);
+        // Atom should show roaming, despite type being unknown
+        assertEquals(ServiceState.ROAMING_TYPE_UNKNOWN, state.voiceRoamingType);
+        assertEquals(ServiceState.ROAMING_TYPE_UNKNOWN, state.dataRoamingType);
+        assertFalse(state.isEndc);
+        assertEquals(0, state.simSlotIndex);
+        assertFalse(state.isMultiSim);
+        assertEquals(CARRIER1_ID, state.carrierId);
+        assertEquals(400L, state.totalTimeMillis);
+        assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
+        CellularDataServiceSwitch serviceSwitch = serviceSwitchCaptor.getAllValues().get(0);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, serviceSwitch.ratFrom);
+        assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, serviceSwitch.ratTo);
+        assertEquals(0, serviceSwitch.simSlotIndex);
+        assertFalse(serviceSwitch.isMultiSim);
+        assertEquals(CARRIER1_ID, serviceSwitch.carrierId);
+        assertEquals(1, serviceSwitch.switchCount);
+        assertNull(serviceSwitchCaptor.getAllValues().get(1)); // produced by conclude()
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    @SmallTest
     public void onServiceStateChanged_dualSim() throws Exception {
         // Using default service state for LTE
         // Only difference between the 2 slots is slot index
@@ -860,6 +973,7 @@
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         state = serviceStateCaptor.getAllValues().get(1);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
@@ -872,6 +986,7 @@
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         state = serviceStateCaptor.getAllValues().get(2);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.dataRat);
@@ -884,6 +999,7 @@
         assertEquals(200L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         state = serviceStateCaptor.getAllValues().get(3);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.dataRat);
@@ -896,6 +1012,7 @@
         assertEquals(200L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         CellularDataServiceSwitch serviceSwitch = serviceSwitchCaptor.getAllValues().get(0);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, serviceSwitch.ratFrom);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, serviceSwitch.ratTo);
@@ -957,6 +1074,7 @@
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         state = captor.getAllValues().get(1);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
@@ -969,6 +1087,7 @@
         assertEquals(200L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
         assertEquals(true, state.isInternetPdnUp);
+        assertEquals(true, state.isDataEnabled);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -1036,6 +1155,141 @@
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
+    @Test
+    @SmallTest
+    public void isNetworkRoaming_nullServiceState() throws Exception {
+        boolean result = ServiceStateStats.isNetworkRoaming(null);
+
+        assertEquals(false, result);
+    }
+
+    @Test
+    @SmallTest
+    public void isNetworkRoaming_notRoaming() throws Exception {
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UMTS)
+                // This sets mNetworkRegistrationState
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .build();
+        nri.setRoamingType(ServiceState.ROAMING_TYPE_NOT_ROAMING);
+        doReturn(nri).when(mServiceState).getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        doReturn(nri).when(mServiceState).getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        boolean result = ServiceStateStats.isNetworkRoaming(mServiceState);
+        boolean resultCs = ServiceStateStats.isNetworkRoaming(
+                mServiceState, NetworkRegistrationInfo.DOMAIN_CS);
+        boolean resultPs = ServiceStateStats.isNetworkRoaming(
+                mServiceState, NetworkRegistrationInfo.DOMAIN_PS);
+
+        assertEquals(false, result);
+        assertEquals(false, resultCs);
+        assertEquals(false, resultPs);
+    }
+
+    @Test
+    @SmallTest
+    public void isNetworkRoaming_csRoaming() throws Exception {
+        NetworkRegistrationInfo roamingNri = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UMTS)
+                // This sets mNetworkRegistrationState
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING)
+                .build();
+        roamingNri.setRoamingType(ServiceState.ROAMING_TYPE_NOT_ROAMING);
+        doReturn(roamingNri).when(mServiceState).getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        boolean result = ServiceStateStats.isNetworkRoaming(mServiceState);
+        boolean resultCs = ServiceStateStats.isNetworkRoaming(
+                mServiceState, NetworkRegistrationInfo.DOMAIN_CS);
+        boolean resultPs = ServiceStateStats.isNetworkRoaming(
+                mServiceState, NetworkRegistrationInfo.DOMAIN_PS);
+
+        assertEquals(true, result);
+        assertEquals(true, resultCs);
+        assertEquals(false, resultPs);
+    }
+
+    @Test
+    @SmallTest
+    public void isNetworkRoaming_psRoaming() throws Exception {
+        NetworkRegistrationInfo roamingNri = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UMTS)
+                // This sets mNetworkRegistrationState
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING)
+                .build();
+        roamingNri.setRoamingType(ServiceState.ROAMING_TYPE_NOT_ROAMING);
+        doReturn(roamingNri).when(mServiceState).getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        boolean result = ServiceStateStats.isNetworkRoaming(mServiceState);
+        boolean resultCs = ServiceStateStats.isNetworkRoaming(
+                mServiceState, NetworkRegistrationInfo.DOMAIN_CS);
+        boolean resultPs = ServiceStateStats.isNetworkRoaming(
+                mServiceState, NetworkRegistrationInfo.DOMAIN_PS);
+
+        assertEquals(true, result);
+        assertEquals(false, resultCs);
+        assertEquals(true, resultPs);
+    }
+
+    @Test
+    @SmallTest
+    public void isNetworkRoaming_bothRoaming() throws Exception {
+        NetworkRegistrationInfo roamingNri = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UMTS)
+                // This sets mNetworkRegistrationState
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING)
+                .build();
+        roamingNri.setRoamingType(ServiceState.ROAMING_TYPE_NOT_ROAMING);
+        doReturn(roamingNri).when(mServiceState).getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        doReturn(roamingNri).when(mServiceState).getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        boolean result = ServiceStateStats.isNetworkRoaming(mServiceState);
+        boolean resultCs = ServiceStateStats.isNetworkRoaming(
+                mServiceState, NetworkRegistrationInfo.DOMAIN_CS);
+        boolean resultPs = ServiceStateStats.isNetworkRoaming(
+                mServiceState, NetworkRegistrationInfo.DOMAIN_PS);
+
+        assertEquals(true, result);
+        assertEquals(true, resultCs);
+        assertEquals(true, resultPs);
+    }
+
+    @Test
+    @SmallTest
+    public void onVoiceServiceStateOverrideChanged_voiceCallingCapabilityChange() {
+        // Using default service state for LTE
+        mServiceStateStats.onServiceStateChanged(mServiceState);
+        mServiceStateStats.incTimeMillis(100L);
+        // Voice Calling registered
+        mServiceStateStats.onVoiceServiceStateOverrideChanged(true);
+        mServiceStateStats.incTimeMillis(100L);
+        // Voice Calling unregistered
+        mServiceStateStats.onVoiceServiceStateOverrideChanged(false);
+        mServiceStateStats.incTimeMillis(100L);
+        // Voice Calling unregistered again. Same state should not generate a new atom
+        mServiceStateStats.onVoiceServiceStateOverrideChanged(false);
+        mServiceStateStats.incTimeMillis(100L);
+
+        // There should be 3 service state updates
+        mServiceStateStats.conclude();
+        ArgumentCaptor<CellularServiceState> captor =
+                ArgumentCaptor.forClass(CellularServiceState.class);
+        verify(mPersistAtomsStorage, times(3))
+                .addCellularServiceStateAndCellularDataServiceSwitch(captor.capture(), eq(null));
+        CellularServiceState state = captor.getAllValues().get(0);
+        assertEquals(false, state.overrideVoiceService);
+        state = captor.getAllValues().get(1);
+        assertEquals(true, state.overrideVoiceService);
+        state = captor.getAllValues().get(2);
+        assertEquals(false, state.overrideVoiceService);
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
     private void mockWwanPsRat(@NetworkType int rat) {
         mockWwanRat(
                 NetworkRegistrationInfo.DOMAIN_PS,
@@ -1088,6 +1342,7 @@
         doReturn(1).when(mSecondPhone).getPhoneId();
         doReturn(1).when(mUiccController).getSlotIdFromPhoneId(1);
         doReturn(carrierId).when(mSecondPhone).getCarrierId();
+        doReturn(mDataSettingsManager).when(mSecondPhone).getDataSettingsManager();
 
         doReturn(true).when(mPhysicalSlot1).isActive();
         doReturn(CardState.CARDSTATE_PRESENT).when(mPhysicalSlot1).getCardState();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
index 2ca0b16..b358b6d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
@@ -44,13 +44,16 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
+import android.os.Looper;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation.NetworkType;
 import android.telephony.DisconnectCause;
 import android.telephony.NetworkRegistrationInfo;
+import android.telephony.PreciseDataConnectionState;
 import android.telephony.PreciseDisconnectCause;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsStreamMediaProfile;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -155,19 +158,17 @@
         mCsCall1 = mock(GsmCdmaCall.class);
         mImsCall0 = mock(ImsPhoneCall.class);
         mImsCall1 = mock(ImsPhoneCall.class);
-
         replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone, mSecondPhone});
         doReturn(CARRIER_ID_SLOT_0).when(mPhone).getCarrierId();
-        // mPhone's mSST/mServiceState has been set up by TelephonyTest
+        // mPhone's mContext/mSST/mServiceState has been set up by TelephonyTest
         doReturn(CARRIER_ID_SLOT_1).when(mSecondPhone).getCarrierId();
+        doReturn(mContext).when(mSecondPhone).getContext();
         doReturn(mSignalStrength).when(mSecondPhone).getSignalStrength();
         doReturn(mSecondServiceStateTracker).when(mSecondPhone).getServiceStateTracker();
         doReturn(mSecondServiceState).when(mSecondServiceStateTracker).getServiceState();
 
         setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_UNKNOWN);
-        doReturn(false).when(mServiceState).getVoiceRoaming();
         setServiceState(mSecondServiceState, TelephonyManager.NETWORK_TYPE_UNKNOWN);
-        doReturn(false).when(mSecondServiceState).getVoiceRoaming();
 
         doReturn(true).when(mPhysicalSlot).isActive();
         doReturn(CardState.CARDSTATE_PRESENT).when(mPhysicalSlot).getCardState();
@@ -196,6 +197,10 @@
         doReturn(PhoneConstants.PHONE_TYPE_GSM).when(mGsmConnection1).getPhoneType();
         doReturn(false).when(mGsmConnection1).isEmergencyCall();
 
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
         mVoiceCallSessionStats0 = new TestableVoiceCallSessionStats(0, mPhone);
         mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
         mVoiceCallSessionStats1 = new TestableVoiceCallSessionStats(1, mSecondPhone);
@@ -204,6 +209,8 @@
 
     @After
     public void tearDown() throws Exception {
+        DataConnectionStateTracker.getInstance(0).stop();
+        DataConnectionStateTracker.getInstance(1).stop();
         mVoiceCallSessionStats0 = null;
         mVoiceCallSessionStats1 = null;
         super.tearDown();
@@ -754,9 +761,17 @@
     @SmallTest
     public void singleImsCall_roaming() {
         setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_LTE);
+        NetworkRegistrationInfo roamingNri = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                // This sets mNetworkRegistrationState
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING)
+                .build();
+        doReturn(roamingNri).when(mServiceState)
+                .getNetworkRegistrationInfo(
+                        NetworkRegistrationInfo.DOMAIN_CS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
         doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mImsStats).getImsVoiceRadioTech();
         doReturn(mImsPhone).when(mPhone).getImsPhone();
-        doReturn(true).when(mServiceState).getVoiceRoaming();
         doReturn(true).when(mImsConnection0).isIncoming();
         doReturn(2000L).when(mImsConnection0).getCreateTime();
         doReturn(mImsCall0).when(mImsConnection0).getCall();
@@ -870,6 +885,7 @@
         expectedCall.mainCodecQuality =
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
         expectedCall.ratSwitchCount = 2L;
+        expectedCall.ratSwitchCountAfterConnected = 2L;
         expectedCall.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
         expectedCall.bandAtEnd = 0;
         expectedCall.callDuration =
@@ -947,6 +963,7 @@
         expectedCall.mainCodecQuality =
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
         expectedCall.ratSwitchCount = 3L;
+        expectedCall.ratSwitchCountAfterConnected = 3L;
         expectedCall.ratAtEnd = TelephonyManager.NETWORK_TYPE_UNKNOWN;
         expectedCall.bandAtEnd = 0;
         expectedCall.callDuration =
@@ -1139,6 +1156,7 @@
         expectedCall0.concurrentCallCountAtStart = 0;
         expectedCall0.concurrentCallCountAtEnd = 1;
         expectedCall0.ratSwitchCount = 1L;
+        expectedCall0.ratSwitchCountAfterConnected = 1L;
         expectedCall0.ratAtEnd = TelephonyManager.NETWORK_TYPE_HSPA;
         expectedCall0.bandAtEnd = 0;
         expectedCall0.lastKnownRat = TelephonyManager.NETWORK_TYPE_HSPA;
@@ -1161,6 +1179,7 @@
         expectedCall1.concurrentCallCountAtStart = 1;
         expectedCall1.concurrentCallCountAtEnd = 0;
         expectedCall1.ratSwitchCount = 2L;
+        expectedCall1.ratSwitchCountAfterConnected = 2L;
         expectedCall1.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
         expectedCall1.bandAtEnd = 0;
         expectedCall1.lastKnownRat = TelephonyManager.NETWORK_TYPE_UMTS;
@@ -1269,6 +1288,7 @@
         expectedCall0.concurrentCallCountAtStart = 0;
         expectedCall0.concurrentCallCountAtEnd = 0;
         expectedCall0.ratSwitchCount = 2L;
+        expectedCall0.ratSwitchCountAfterConnected = 2L;
         expectedCall0.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
         expectedCall0.bandAtEnd = 0;
         expectedCall0.lastKnownRat = TelephonyManager.NETWORK_TYPE_UMTS;
@@ -1291,6 +1311,7 @@
         expectedCall1.concurrentCallCountAtStart = 1;
         expectedCall1.concurrentCallCountAtEnd = 1;
         expectedCall1.ratSwitchCount = 1L;
+        expectedCall1.ratSwitchCountAfterConnected = 1L;
         expectedCall1.ratAtEnd = TelephonyManager.NETWORK_TYPE_HSPA;
         expectedCall1.bandAtEnd = 0;
         expectedCall1.lastKnownRat = TelephonyManager.NETWORK_TYPE_HSPA;
@@ -1399,6 +1420,7 @@
         expectedCall0.concurrentCallCountAtStart = 0;
         expectedCall0.concurrentCallCountAtEnd = 1;
         expectedCall0.ratSwitchCount = 0L;
+        expectedCall0.ratSwitchCountAfterConnected = 0L;
         expectedCall0.ratAtEnd = TelephonyManager.NETWORK_TYPE_LTE;
         // call 1 starts later, MT
         doReturn(true).when(mImsConnection1).isIncoming();
@@ -1419,6 +1441,7 @@
         expectedCall1.concurrentCallCountAtStart = 1;
         expectedCall1.concurrentCallCountAtEnd = 0;
         expectedCall1.ratSwitchCount = 1L;
+        expectedCall1.ratSwitchCountAfterConnected = 1L;
         expectedCall1.ratAtEnd = TelephonyManager.NETWORK_TYPE_HSPA;
         expectedCall1.bandAtEnd = 0;
         expectedCall1.lastKnownRat = TelephonyManager.NETWORK_TYPE_HSPA;
@@ -1505,6 +1528,7 @@
         expectedCall.setupDurationMillis = 5000;
         expectedCall.disconnectExtraCode = PreciseDisconnectCause.CALL_REJECTED;
         expectedCall.ratSwitchCount = 1L;
+        expectedCall.ratSwitchCountAfterConnected = 0L;
         expectedCall.setupFailed = true;
         expectedCall.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN;
         expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
@@ -1567,6 +1591,7 @@
         expectedCall.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
         expectedCall.bandAtEnd = 0;
         expectedCall.ratSwitchCount = 1L;
+        expectedCall.ratSwitchCountAfterConnected = 0L;
         expectedCall.setupFailed = true;
         expectedCall.setupDurationMillis = 13000;
         expectedCall.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN;
@@ -1626,6 +1651,7 @@
         expectedCall.setupDurationMillis = 5000;
         expectedCall.disconnectExtraCode = PreciseDisconnectCause.NORMAL;
         expectedCall.ratSwitchCount = 1L;
+        expectedCall.ratSwitchCountAfterConnected = 0L;
         expectedCall.setupFailed = false;
         expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
         expectedCall.mainCodecQuality =
@@ -1805,6 +1831,7 @@
         expectedCall.setupFailed = false;
         expectedCall.srvccFailureCount = 2L;
         expectedCall.ratSwitchCount = 1L;
+        expectedCall.ratSwitchCountAfterConnected = 1L;
         expectedCall.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
         expectedCall.bandAtEnd = 0;
         expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
@@ -1957,6 +1984,7 @@
         expectedCall.srvccCompleted = true;
         expectedCall.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
         expectedCall.ratSwitchCount = 1L;
+        expectedCall.ratSwitchCountAfterConnected = 1L;
         expectedCall.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
         expectedCall.bandAtEnd = 0;
         expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
@@ -2055,6 +2083,7 @@
         expectedCall0.concurrentCallCountAtStart = 0;
         expectedCall0.concurrentCallCountAtEnd = 1;
         expectedCall0.ratSwitchCount = 1L;
+        expectedCall0.ratSwitchCountAfterConnected = 1L;
         expectedCall0.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
         expectedCall0.bandAtEnd = 0;
         expectedCall0.srvccCompleted = true;
@@ -2082,6 +2111,7 @@
         expectedCall1.concurrentCallCountAtStart = 1;
         expectedCall1.concurrentCallCountAtEnd = 0;
         expectedCall1.ratSwitchCount = 1L;
+        expectedCall1.ratSwitchCountAfterConnected = 1L;
         expectedCall1.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
         expectedCall1.bandAtEnd = 0;
         expectedCall1.srvccCompleted = true;
@@ -2163,6 +2193,322 @@
 
     @Test
     @SmallTest
+    public void singleCsCall_handover() {
+        doReturn(false).when(mGsmConnection0).isIncoming();
+        doReturn(2000L).when(mGsmConnection0).getCreateTime();
+        doReturn(mCsCall0).when(mGsmConnection0).getCall();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        DisconnectCause.NORMAL);
+        expectedCall.ratAtConnected = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall.bandAtEnd = 0;
+        expectedCall.setupDurationMillis = 5000;
+        expectedCall.disconnectExtraCode = PreciseDisconnectCause.NORMAL;
+        expectedCall.ratSwitchCount = 1L;
+        expectedCall.ratSwitchCountAfterConnected = 0L;
+        expectedCall.setupFailed = false;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall.mainCodecQuality =
+                VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
+        expectedCall.lastKnownRat = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall.handoverInProgress = false;
+        VoiceCallRatUsage expectedRatUsageLte =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 3000L, 1L);
+        VoiceCallRatUsage expectedRatUsageUmts =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_UMTS, 3000L, 12000L, 1L);
+        final AtomicReference<VoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_LTE);
+        doReturn(Call.State.DIALING).when(mCsCall0).getState();
+        doReturn(Call.State.DIALING).when(mGsmConnection0).getState();
+        doReturn(DisconnectCause.NOT_DISCONNECTED).when(mGsmConnection0).getDisconnectCause();
+        mVoiceCallSessionStats0.onRilDial(mGsmConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(3000L);
+        setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_UMTS);
+        mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
+        mVoiceCallSessionStats0.setTimeMillis(3100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(mGsmConnection0, DriverCall.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(7000L);
+        doReturn(Call.State.ALERTING).when(mCsCall0).getState();
+        doReturn(Call.State.ALERTING).when(mGsmConnection0).getState();
+        mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(10000L);
+        doReturn(Call.State.ACTIVE).when(mCsCall0).getState();
+        doReturn(Call.State.ACTIVE).when(mGsmConnection0).getState();
+        mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(11000L);
+        // connection state changes for IMS APN shouldn't have impact on cs call
+        mVoiceCallSessionStats0.onPreciseDataConnectionStateChanged(
+                makePreciseDataConnectionState(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                        TelephonyManager.DATA_HANDOVER_IN_PROGRESS, ApnSetting.TYPE_IMS));
+        mVoiceCallSessionStats0.setTimeMillis(12000L);
+        doReturn(DisconnectCause.NORMAL).when(mGsmConnection0).getDisconnectCause();
+        doReturn(PreciseDisconnectCause.NORMAL).when(mGsmConnection0).getPreciseDisconnectCause();
+        mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertSortedProtoArrayEquals(
+                new VoiceCallRatUsage[] {expectedRatUsageLte, expectedRatUsageUmts},
+                ratUsage.get());
+    }
+
+    @Test
+    @SmallTest
+    public void singleCall_callStartedDuringHandover() {
+        // set last IMS APN connection state with handover in progress
+        DataConnectionStateTracker.getInstance(0).notifyDataConnectionStateChanged(
+                makePreciseDataConnectionState(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                        TelephonyManager.DATA_HANDOVER_IN_PROGRESS, ApnSetting.TYPE_IMS));
+        setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_LTE);
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mImsStats).getImsVoiceRadioTech();
+        doReturn(true).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        doReturn(mImsPhone).when(mPhone).getImsPhone();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_USER_TERMINATED);
+        expectedCall.setupDurationMillis = 80;
+        expectedCall.setupFailed = false;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall.mainCodecQuality =
+                VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
+        expectedCall.handoverInProgress = true;
+        VoiceCallRatUsage expectedRatUsage =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L);
+        final AtomicReference<VoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(2200L);
+        mVoiceCallSessionStats0.onImsAcceptCall(List.of(mImsConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(2280L);
+        doReturn(Call.State.ACTIVE).when(mImsCall0).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        mVoiceCallSessionStats0.setTimeMillis(12000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertThat(ratUsage.get()).hasLength(1);
+        assertProtoEquals(expectedRatUsage, ratUsage.get()[0]);
+    }
+
+    @Test
+    @SmallTest
+    public void singleCall_callTerminatedDuringHandover() {
+        setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_LTE);
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mImsStats).getImsVoiceRadioTech();
+        doReturn(true).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        doReturn(mImsPhone).when(mPhone).getImsPhone();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_USER_TERMINATED);
+        expectedCall.setupDurationMillis = 80;
+        expectedCall.setupFailed = false;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall.mainCodecQuality =
+                VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
+        expectedCall.handoverInProgress = true;
+        VoiceCallRatUsage expectedRatUsage =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L);
+        final AtomicReference<VoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(2200L);
+        mVoiceCallSessionStats0.onImsAcceptCall(List.of(mImsConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(2280L);
+        doReturn(Call.State.ACTIVE).when(mImsCall0).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        mVoiceCallSessionStats0.setTimeMillis(9500L);
+        mVoiceCallSessionStats0.onPreciseDataConnectionStateChanged(
+                makePreciseDataConnectionState(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                        TelephonyManager.DATA_HANDOVER_IN_PROGRESS, ApnSetting.TYPE_IMS));
+        mVoiceCallSessionStats0.setTimeMillis(12000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertThat(ratUsage.get()).hasLength(1);
+        assertProtoEquals(expectedRatUsage, ratUsage.get()[0]);
+    }
+
+    @Test
+    @SmallTest
+    public void singleCall_handoverSuccess() {
+        setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_LTE);
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mImsStats).getImsVoiceRadioTech();
+        doReturn(true).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        doReturn(mImsPhone).when(mPhone).getImsPhone();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_USER_TERMINATED);
+        expectedCall.setupDurationMillis = 80;
+        expectedCall.setupFailed = false;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall.mainCodecQuality =
+                VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
+        expectedCall.handoverInProgress = false;
+        VoiceCallRatUsage expectedRatUsage =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L);
+        final AtomicReference<VoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(2200L);
+        mVoiceCallSessionStats0.onImsAcceptCall(List.of(mImsConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(2280L);
+        doReturn(Call.State.ACTIVE).when(mImsCall0).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        mVoiceCallSessionStats0.setTimeMillis(9500L);
+        mVoiceCallSessionStats0.onPreciseDataConnectionStateChanged(
+                makePreciseDataConnectionState(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                        TelephonyManager.DATA_HANDOVER_IN_PROGRESS, ApnSetting.TYPE_IMS));
+        mVoiceCallSessionStats0.setTimeMillis(11000L);
+        mVoiceCallSessionStats0.onPreciseDataConnectionStateChanged(
+                makePreciseDataConnectionState(AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+                        TelephonyManager.DATA_CONNECTED, ApnSetting.TYPE_IMS));
+        mVoiceCallSessionStats0.setTimeMillis(12000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertThat(ratUsage.get()).hasLength(1);
+        assertProtoEquals(expectedRatUsage, ratUsage.get()[0]);
+    }
+
+    @Test
+    @SmallTest
+    public void singleEmergencyCall_callTerminatedDuringHandover() {
+        setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_LTE);
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mImsStats).getImsVoiceRadioTech();
+        doReturn(true).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(true).when(mImsConnection0).isEmergencyCall();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        doReturn(mImsPhone).when(mPhone).getImsPhone();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_USER_TERMINATED);
+        expectedCall.setupDurationMillis = 80;
+        expectedCall.setupFailed = false;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall.mainCodecQuality =
+                VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
+        expectedCall.isEmergency = true;
+        expectedCall.handoverInProgress = true;
+        VoiceCallRatUsage expectedRatUsage =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L);
+        final AtomicReference<VoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(2200L);
+        mVoiceCallSessionStats0.onImsAcceptCall(List.of(mImsConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(2280L);
+        doReturn(Call.State.ACTIVE).when(mImsCall0).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        mVoiceCallSessionStats0.setTimeMillis(9500L);
+        mVoiceCallSessionStats0.onPreciseDataConnectionStateChanged(
+                makePreciseDataConnectionState(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                        TelephonyManager.DATA_HANDOVER_IN_PROGRESS, ApnSetting.TYPE_EMERGENCY));
+        mVoiceCallSessionStats0.setTimeMillis(10000L);
+        // connection state changes for IMS APN shouldn't have impact on emergency call
+        mVoiceCallSessionStats0.onPreciseDataConnectionStateChanged(
+                makePreciseDataConnectionState(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                        TelephonyManager.DATA_CONNECTED, ApnSetting.TYPE_IMS));
+        mVoiceCallSessionStats0.setTimeMillis(12000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertThat(ratUsage.get()).hasLength(1);
+        assertProtoEquals(expectedRatUsage, ratUsage.get()[0]);
+    }
+
+    @Test
+    @SmallTest
     public void singleWifiCall_preferred() {
         setServiceStateWithWifiCalling(mServiceState, TelephonyManager.NETWORK_TYPE_LTE);
         doReturn(mImsPhone).when(mPhone).getImsPhone();
@@ -2329,6 +2675,7 @@
         call.lastKnownRat = rat;
         call.bandAtEnd = 1;
         call.ratSwitchCount = 0L;
+        call.ratSwitchCountAfterConnected = 0L;
         call.codecBitmask = 0L;
         call.mainCodecQuality = VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN;
         call.simSlotIndex = 0;
@@ -2363,6 +2710,7 @@
         call.lastKnownRat = rat;
         call.bandAtEnd = 1;
         call.ratSwitchCount = 0L;
+        call.ratSwitchCountAfterConnected = 0L;
         call.codecBitmask = 0L;
         call.mainCodecQuality = VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN;
         call.simSlotIndex = 1;
@@ -2390,6 +2738,22 @@
         return usage;
     }
 
+    private static PreciseDataConnectionState makePreciseDataConnectionState(int transport,
+            int state, int apnType) {
+        return new PreciseDataConnectionState.Builder()
+                .setTransportType(transport)
+                .setId(1)
+                .setState(state)
+                .setApnSetting(new ApnSetting.Builder()
+                        .setApnTypeBitmask(apnType)
+                        .setApnName("ims,emergency")
+                        .setEntryName("ims,emergency")
+                        .build())
+                .setLinkProperties(new android.net.LinkProperties())
+                .setFailCause(0)
+                .build();
+    }
+
     private static void assertProtoEquals(MessageNano expected, MessageNano actual) {
         assertWithMessage(
                         "  actual proto:\n"
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/NtnCapabilityResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/NtnCapabilityResolverTest.java
new file mode 100644
index 0000000..c202f0c
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/NtnCapabilityResolverTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2023 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.internal.telephony.satellite;
+
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
+import static android.telephony.NetworkRegistrationInfo.SERVICE_TYPE_DATA;
+import static android.telephony.NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY;
+import static android.telephony.NetworkRegistrationInfo.SERVICE_TYPE_SMS;
+import static android.telephony.TelephonyManager.NETWORK_TYPE_LTE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+
+import android.annotation.NonNull;
+import android.telephony.CellIdentity;
+import android.telephony.CellIdentityGsm;
+import android.telephony.NetworkRegistrationInfo;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.ArraySet;
+
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NtnCapabilityResolverTest extends TelephonyTest {
+    private static final String TAG = "NtnCapabilityResolverTest";
+    private static final int SUB_ID = 0;
+    private static final String VISITING_PLMN = "00102";
+    private static final String SATELLITE_PLMN = "00103";
+    private static final String[] SATELLITE_PLMN_ARRAY = {SATELLITE_PLMN};
+
+    private final int[] mSatelliteSupportedServices = {SERVICE_TYPE_SMS, SERVICE_TYPE_EMERGENCY};
+    private final List<Integer> mSatelliteSupportedServiceList =
+            Arrays.stream(mSatelliteSupportedServices).boxed().collect(Collectors.toList());
+    @Mock private SatelliteController mMockSatelliteController;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
+        logd(TAG + " Setup!");
+
+        replaceInstance(SatelliteController.class, "sInstance", null,
+                mMockSatelliteController);
+        doReturn(Arrays.asList(SATELLITE_PLMN_ARRAY))
+                .when(mMockSatelliteController).getSatellitePlmnList();
+        doReturn(mSatelliteSupportedServiceList).when(mMockSatelliteController)
+                .getSupportedSatelliteServices(SUB_ID, SATELLITE_PLMN);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        logd(TAG + " tearDown");
+        super.tearDown();
+    }
+
+    @Test
+    public void testResolveNTNCapability() {
+        // Test resolving a satellite NetworkRegistrationInfo.
+        NetworkRegistrationInfo satelliteNri = createNetworkRegistrationInfo(SATELLITE_PLMN);
+        NetworkRegistrationInfo originalNri = new NetworkRegistrationInfo(satelliteNri);
+
+        assertEquals(satelliteNri, originalNri);
+        assertFalse(satelliteNri.isNonTerrestrialNetwork());
+        assertFalse(Arrays.equals(mSatelliteSupportedServices,
+                satelliteNri.getAvailableServices().stream()
+                .mapToInt(Integer::intValue)
+                .toArray()));
+        NtnCapabilityResolver.resolveNtnCapability(satelliteNri, SUB_ID);
+        assertNotEquals(satelliteNri, originalNri);
+        assertTrue(satelliteNri.isNonTerrestrialNetwork());
+        assertTrue(Arrays.equals(mSatelliteSupportedServices,
+                satelliteNri.getAvailableServices().stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+
+        // Test resolving a non-satellite NetworkRegistrationInfo.
+        NetworkRegistrationInfo cellularNri = createNetworkRegistrationInfo(VISITING_PLMN);
+        originalNri = new NetworkRegistrationInfo(cellularNri);
+
+        assertEquals(cellularNri, originalNri);
+        assertFalse(cellularNri.isNonTerrestrialNetwork());
+        assertFalse(Arrays.equals(mSatelliteSupportedServices,
+                cellularNri.getAvailableServices().stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+        NtnCapabilityResolver.resolveNtnCapability(cellularNri, SUB_ID);
+        assertEquals(cellularNri, originalNri);
+        assertFalse(cellularNri.isNonTerrestrialNetwork());
+        assertFalse(Arrays.equals(mSatelliteSupportedServices,
+                cellularNri.getAvailableServices().stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+    }
+
+    private NetworkRegistrationInfo createNetworkRegistrationInfo(@NonNull String registeredPlmn) {
+        List<Integer> availableServices = new ArrayList<>();
+        availableServices.add(SERVICE_TYPE_DATA);
+        CellIdentity cellIdentity = new CellIdentityGsm(0, 0, 0,
+                0, "mcc", "mnc", "", "", new ArraySet<>());
+        return new NetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN,
+                REGISTRATION_STATE_ROAMING, NETWORK_TYPE_LTE, 0, false, availableServices,
+                cellIdentity, registeredPlmn, false, 0, 0, 0);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/PointingAppControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/PointingAppControllerTest.java
new file mode 100644
index 0000000..4587b7c
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/PointingAppControllerTest.java
@@ -0,0 +1,577 @@
+/*
+ * Copyright (C) 2023 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.internal.telephony.satellite;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+import android.os.AsyncResult;
+import android.os.Message;
+import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
+import android.telephony.satellite.PointingInfo;
+import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.SatelliteManager.SatelliteException;
+import android.util.Log;
+
+import com.android.internal.R;
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class PointingAppControllerTest extends TelephonyTest {
+    private static final String TAG = "PointingAppControllerTest";
+    private static final int SUB_ID = 0;
+    private static final long TIMEOUT = 500;
+
+    //Events For SatelliteControllerHandler
+    private static final int EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE = 2;
+    private static final int EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE = 4;
+    private static final String KEY_POINTING_UI_PACKAGE_NAME = "default_pointing_ui_package";
+    private static final String KEY_POINTING_UI_CLASS_NAME = "default_pointing_ui_class";
+    private static final String KEY_NEED_FULL_SCREEN = "needFullScreen";
+
+    private PointingAppController mPointingAppController;
+    InOrder mInOrder;
+
+    @Mock private SatelliteModemInterface mMockSatelliteModemInterface;
+    @Mock private SatelliteController mMockSatelliteController;
+
+    private TestSatelliteTransmissionUpdateCallback mSatelliteTransmissionUpdateCallback;
+    private TestSatelliteControllerHandler mTestSatelliteControllerHandler;
+    /** Variables required to receive datagrams in the unit tests. */
+    LinkedBlockingQueue<Integer> mResultListener;
+    int mResultCode = -1;
+    private Semaphore mSendDatagramStateSemaphore = new Semaphore(0);
+    private Semaphore mReceiveDatagramStateSemaphore = new Semaphore(0);
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
+        logd(TAG + " Setup!");
+
+        replaceInstance(SatelliteModemInterface.class, "sInstance", null,
+                mMockSatelliteModemInterface);
+        replaceInstance(SatelliteController.class, "sInstance", null,
+                mMockSatelliteController);
+        mPointingAppController = new PointingAppController(mContext);
+        mContextFixture.putResource(R.string.config_pointing_ui_package,
+                KEY_POINTING_UI_PACKAGE_NAME);
+        mContextFixture.putResource(R.string.config_pointing_ui_class,
+                KEY_POINTING_UI_CLASS_NAME);
+        mResultListener = new LinkedBlockingQueue<>(1);
+        mSatelliteTransmissionUpdateCallback = new TestSatelliteTransmissionUpdateCallback();
+        mTestSatelliteControllerHandler = new TestSatelliteControllerHandler();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        logd(TAG + " tearDown");
+        mResultListener = null;
+
+        mSatelliteTransmissionUpdateCallback = null;
+        super.tearDown();
+    }
+
+    private class TestSatelliteControllerHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            AsyncResult ar;
+
+            switch(msg.what) {
+                case EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE: {
+                    ar = (AsyncResult) msg.obj;
+                    mResultCode =  SatelliteServiceUtils.getSatelliteError(ar,
+                            "startSatelliteTransmissionUpdates");
+                    logd("mResultCode = " + mResultCode);
+                    break;
+                }
+                case EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE: {
+                    ar = (AsyncResult) msg.obj;
+                    mResultCode =  SatelliteServiceUtils.getSatelliteError(ar,
+                            "stopSatelliteTransmissionUpdates");
+                    logd("mResultCode = " + mResultCode);
+                    break;
+                }
+                default:
+                    logd("TestSatelliteController: " + msg.what);
+                    break;
+            }
+        }
+    }
+    private class TestSatelliteTransmissionUpdateCallback
+                                extends ISatelliteTransmissionUpdateCallback.Stub {
+        int mState;
+        int mSendPendingCount;
+        int mReceivePendingCount;
+        int mErrorCode;
+        public boolean inSendDatagramStateCallback = false;
+        public boolean inReceiveDatagramStateCallback = false;
+
+        @Override
+        public void onSatellitePositionChanged(PointingInfo pointingInfo) {
+            //Not Used
+        }
+
+        @Override
+        public void onSendDatagramStateChanged(int state, int sendPendingCount,
+                                    int errorCode) {
+            mState = state;
+            mSendPendingCount = sendPendingCount;
+            mErrorCode = errorCode;
+            inSendDatagramStateCallback = true;
+            try {
+                mSendDatagramStateSemaphore.release();
+            } catch (Exception ex) {
+                loge("mSendDatagramStateSemaphore: Got exception in releasing semaphore, ex=" + ex);
+            }
+        }
+
+        @Override
+        public void onReceiveDatagramStateChanged(int state,
+                            int receivePendingCount, int errorCode) {
+            mState = state;
+            mReceivePendingCount = receivePendingCount;
+            mErrorCode = errorCode;
+            inReceiveDatagramStateCallback = true;
+            try {
+                mReceiveDatagramStateSemaphore.release();
+            } catch (Exception ex) {
+                loge("mReceiveDatagramStateSemaphore: Got exception in releasing semaphore, ex="
+                        + ex);
+            }
+        }
+
+        public int getState() {
+            return mState;
+        }
+
+        public int getSendPendingCount() {
+            return mSendPendingCount;
+        }
+
+        public int getReceivePendingCount() {
+            return mReceivePendingCount;
+        }
+
+        public int getErrorCode() {
+            return mErrorCode;
+        }
+    }
+
+    private boolean waitForReceiveDatagramStateChangedRessult(
+            int expectedNumberOfEvents) {
+        for (int i = 0; i < expectedNumberOfEvents; i++) {
+            try {
+                if (!mReceiveDatagramStateSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) {
+                    loge("Timeout to receive "
+                            + "ReceiveDatagramStateChanged event");
+                    return false;
+                }
+            } catch (Exception ex) {
+                loge("waitForReceiveDatagramStateChangedRessult: Got exception=" + ex);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean waitForSendDatagramStateChangedRessult(
+            int expectedNumberOfEvents) {
+        for (int i = 0; i < expectedNumberOfEvents; i++) {
+            try {
+                if (!mSendDatagramStateSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) {
+                    loge("Timeout to receive "
+                            + "SendDatagramStateChanged event");
+                    return false;
+                }
+            } catch (Exception ex) {
+                loge("waitForSendDatagramStateChangedRessult: Got exception=" + ex);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void setUpResponseForStartTransmissionUpdates(
+            @SatelliteManager.SatelliteError int error) {
+        SatelliteException exception = (error == SatelliteManager.SATELLITE_ERROR_NONE)
+                ? null : new SatelliteException(error);
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+            AsyncResult.forMessage(message, null, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mPhone).startSatellitePositionUpdates(any(Message.class));
+
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+            AsyncResult.forMessage(message, null, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface).startSendingSatellitePointingInfo(any(Message.class));
+    }
+
+    private void setUpResponseForStopTransmissionUpdates(
+            @SatelliteManager.SatelliteError int error) {
+        SatelliteException exception = (error == SatelliteManager.SATELLITE_ERROR_NONE)
+                ? null : new SatelliteException(error);
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+            AsyncResult.forMessage(message, null, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mPhone).stopSatellitePositionUpdates(any(Message.class));
+
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+            AsyncResult.forMessage(message, null, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface).stopSendingSatellitePointingInfo(any(Message.class));
+    }
+
+    @Test
+    public void testStartSatelliteTransmissionUpdates_CommandInterface()
+            throws Exception {
+        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        Message testMessage = mTestSatelliteControllerHandler
+                .obtainMessage(EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE, null);
+        setUpResponseForStartTransmissionUpdates(SatelliteManager.SATELLITE_ERROR_NONE);
+        mPointingAppController.startSatelliteTransmissionUpdates(testMessage, mPhone);
+
+        processAllMessages();
+
+        verify(mMockSatelliteModemInterface, never())
+                .startSendingSatellitePointingInfo(eq(testMessage));
+
+        verify(mPhone)
+                .startSatellitePositionUpdates(eq(testMessage));
+
+        assertEquals(SatelliteManager.SATELLITE_ERROR_NONE, mResultCode);
+
+        assertTrue(mPointingAppController.getStartedSatelliteTransmissionUpdates());
+    }
+
+    @Test
+    public void testStartSatelliteTransmissionUpdates_success()
+            throws Exception {
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        mPointingAppController.setStartedSatelliteTransmissionUpdates(false);
+        Message testMessage = mTestSatelliteControllerHandler
+                .obtainMessage(EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE, null);
+        setUpResponseForStartTransmissionUpdates(SatelliteManager.SATELLITE_ERROR_NONE);
+        mPointingAppController.startSatelliteTransmissionUpdates(testMessage, mPhone);
+
+        verify(mMockSatelliteModemInterface)
+                .startSendingSatellitePointingInfo(eq(testMessage));
+
+        verify(mPhone, never())
+                .startSatellitePositionUpdates(eq(testMessage));
+
+        processAllMessages();
+
+
+        assertTrue(mPointingAppController.getStartedSatelliteTransmissionUpdates());
+        assertEquals(SatelliteManager.SATELLITE_ERROR_NONE, mResultCode);
+    }
+
+    @Test
+    public void testStartSatelliteTransmissionUpdates_phoneNull()
+            throws Exception {
+        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        mPointingAppController.setStartedSatelliteTransmissionUpdates(false);
+        Message testMessage = mTestSatelliteControllerHandler
+                .obtainMessage(EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE, null);
+
+        mPointingAppController.startSatelliteTransmissionUpdates(testMessage, null);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, never())
+                .startSendingSatellitePointingInfo(eq(testMessage));
+
+        verify(mPhone, never())
+                .startSatellitePositionUpdates(eq(testMessage));
+
+        assertFalse(mPointingAppController.getStartedSatelliteTransmissionUpdates());
+
+        assertEquals(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, mResultCode);
+    }
+
+    @Test
+    public void testStopSatelliteTransmissionUpdates_CommandInterface()
+            throws Exception {
+        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        setUpResponseForStopTransmissionUpdates(SatelliteManager.SATELLITE_ERROR_NONE);
+        Message testMessage = mTestSatelliteControllerHandler
+                .obtainMessage(EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE, null);
+        mPointingAppController.stopSatelliteTransmissionUpdates(testMessage, mPhone);
+
+        processAllMessages();
+
+        verify(mMockSatelliteModemInterface, never())
+                .stopSendingSatellitePointingInfo(eq(testMessage));
+
+        verify(mPhone)
+                .stopSatellitePositionUpdates(eq(testMessage));
+
+        assertFalse(mPointingAppController.getStartedSatelliteTransmissionUpdates());
+
+        assertEquals(SatelliteManager.SATELLITE_ERROR_NONE, mResultCode);
+    }
+
+    @Test
+    public void testStopSatelliteTransmissionUpdates_success()
+            throws Exception {
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        setUpResponseForStopTransmissionUpdates(SatelliteManager.SATELLITE_ERROR_NONE);
+        Message testMessage = mTestSatelliteControllerHandler
+                .obtainMessage(EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE, null);
+        mPointingAppController.stopSatelliteTransmissionUpdates(testMessage, mPhone);
+
+        processAllMessages();
+
+        verify(mMockSatelliteModemInterface)
+                .stopSendingSatellitePointingInfo(eq(testMessage));
+
+        verify(mPhone, never())
+                .stopSatellitePositionUpdates(eq(testMessage));
+
+        assertFalse(mPointingAppController.getStartedSatelliteTransmissionUpdates());
+        assertEquals(SatelliteManager.SATELLITE_ERROR_NONE, mResultCode);
+    }
+
+    @Test
+    public void testStopSatellitePointingInfo_phoneNull()
+            throws Exception {
+        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        Message testMessage = mTestSatelliteControllerHandler
+                .obtainMessage(EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE, null);
+        mPointingAppController.stopSatelliteTransmissionUpdates(testMessage, null);
+
+        processAllMessages();
+
+        verify(mMockSatelliteModemInterface, never())
+                .stopSendingSatellitePointingInfo(eq(testMessage));
+
+        verify(mPhone, never())
+                .stopSatellitePositionUpdates(eq(testMessage));
+
+        assertEquals(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, mResultCode);
+
+    }
+
+    @Test
+    public void testStartPointingUI() throws Exception {
+        ArgumentCaptor<Intent> startedIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        mPointingAppController.startPointingUI(true);
+        verify(mContext).startActivity(startedIntentCaptor.capture());
+        Intent intent = startedIntentCaptor.getValue();
+        assertEquals(KEY_POINTING_UI_PACKAGE_NAME, intent.getComponent().getPackageName());
+        assertEquals(KEY_POINTING_UI_CLASS_NAME, intent.getComponent().getClassName());
+        Bundle b = intent.getExtras();
+        assertTrue(b.containsKey(KEY_NEED_FULL_SCREEN));
+        assertTrue(b.getBoolean(KEY_NEED_FULL_SCREEN));
+    }
+
+    @Test
+    public void testUpdateSendDatagramTransferState() throws Exception {
+        mPointingAppController.registerForSatelliteTransmissionUpdates(SUB_ID,
+                mSatelliteTransmissionUpdateCallback, mPhone);
+        mPointingAppController.updateSendDatagramTransferState(SUB_ID,
+                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS, 1,
+                SatelliteManager.SATELLITE_ERROR_NONE);
+        assertTrue(waitForSendDatagramStateChangedRessult(1));
+        assertEquals(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS,
+                mSatelliteTransmissionUpdateCallback.getState());
+        assertEquals(1, mSatelliteTransmissionUpdateCallback.getSendPendingCount());
+        assertEquals(SatelliteManager.SATELLITE_ERROR_NONE,
+                mSatelliteTransmissionUpdateCallback.getErrorCode());
+        assertTrue(mSatelliteTransmissionUpdateCallback.inSendDatagramStateCallback);
+        mPointingAppController.unregisterForSatelliteTransmissionUpdates(SUB_ID,
+                    mResultListener::offer, mSatelliteTransmissionUpdateCallback, mPhone);
+        mResultListener.clear();
+    }
+
+    @Test
+    public void testUpdateReceiveDatagramTransferState() throws Exception {
+        mPointingAppController.registerForSatelliteTransmissionUpdates(SUB_ID,
+                mSatelliteTransmissionUpdateCallback, mPhone);
+        mPointingAppController.updateReceiveDatagramTransferState(SUB_ID,
+                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS, 2,
+                SatelliteManager.SATELLITE_ERROR_NONE);
+        assertTrue(waitForReceiveDatagramStateChangedRessult(1));
+        assertEquals(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS,
+                mSatelliteTransmissionUpdateCallback.getState());
+        assertEquals(2, mSatelliteTransmissionUpdateCallback.getReceivePendingCount());
+        assertEquals(SatelliteManager.SATELLITE_ERROR_NONE,
+                mSatelliteTransmissionUpdateCallback.getErrorCode());
+        assertTrue(mSatelliteTransmissionUpdateCallback.inReceiveDatagramStateCallback);
+        mPointingAppController.unregisterForSatelliteTransmissionUpdates(SUB_ID,
+                    mResultListener::offer, mSatelliteTransmissionUpdateCallback, mPhone);
+        mResultListener.clear();
+    }
+
+    @Test
+    public void testRegisterForSatelliteTransmissionUpdates_CommandInterface() throws Exception {
+        mResultListener.clear();
+        mInOrder = inOrder(mPhone);
+        TestSatelliteTransmissionUpdateCallback callback1 = new
+                TestSatelliteTransmissionUpdateCallback();
+        TestSatelliteTransmissionUpdateCallback callback2 = new
+                TestSatelliteTransmissionUpdateCallback();
+        int subId1 = 1;
+        int subId2 = 2;
+        mPointingAppController.registerForSatelliteTransmissionUpdates(subId1,
+                callback1, mPhone);
+        mInOrder.verify(mPhone).registerForSatellitePositionInfoChanged(any(),
+                eq(1), eq(null));
+        mPointingAppController.registerForSatelliteTransmissionUpdates(subId1,
+                callback2, mPhone);
+        mInOrder.verify(mPhone, never()).registerForSatellitePositionInfoChanged(any(),
+                eq(1), eq(null));
+        mPointingAppController.registerForSatelliteTransmissionUpdates(subId2,
+                callback1, mPhone);
+        mInOrder.verify(mPhone).registerForSatellitePositionInfoChanged(any(),
+                eq(1), eq(null));
+        mPointingAppController.registerForSatelliteTransmissionUpdates(subId2,
+                callback2, mPhone);
+        mInOrder.verify(mPhone, never()).registerForSatellitePositionInfoChanged(any(),
+                eq(1), eq(null));
+        mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId1,
+                mResultListener::offer, callback1, mPhone);
+        processAllMessages();
+        //since there are 2 callbacks registered for this sub_id, Handler is not unregistered
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
+        mResultListener.remove();
+        mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId1,
+                mResultListener::offer, callback2, mPhone);
+        mInOrder.verify(mPhone).unregisterForSatellitePositionInfoChanged(any(Handler.class));
+        mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId2,
+                mResultListener::offer, callback1, mPhone);
+        processAllMessages();
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
+        mResultListener.remove();
+        mInOrder.verify(mPhone, never()).unregisterForSatellitePositionInfoChanged(
+                any(Handler.class));
+        mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId2,
+                mResultListener::offer, callback2, null);
+        processAllMessages();
+        assertThat(mResultListener.peek())
+                .isEqualTo(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+        mResultListener.remove();
+        mInOrder = null;
+    }
+
+    @Test
+    public void testRegisterForSatelliteTransmissionUpdates() throws Exception {
+        mResultListener.clear();
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        mInOrder = inOrder(mMockSatelliteModemInterface);
+        TestSatelliteTransmissionUpdateCallback callback1 = new
+                TestSatelliteTransmissionUpdateCallback();
+        TestSatelliteTransmissionUpdateCallback callback2 = new
+                TestSatelliteTransmissionUpdateCallback();
+        int subId1 = 3;
+        int subId2 = 4;
+        mPointingAppController.registerForSatelliteTransmissionUpdates(subId1,
+                callback1, mPhone);
+        mInOrder.verify(mMockSatelliteModemInterface).registerForSatellitePositionInfoChanged(any(),
+                eq(1), eq(null));
+        mInOrder.verify(mMockSatelliteModemInterface).registerForDatagramTransferStateChanged(any(),
+                eq(4), eq(null));
+        mPointingAppController.registerForSatelliteTransmissionUpdates(subId1,
+                callback2, mPhone);
+        mInOrder.verify(mMockSatelliteModemInterface, never())
+                .registerForSatellitePositionInfoChanged(any(), eq(1), eq(null));
+        mInOrder.verify(mMockSatelliteModemInterface, never())
+                .registerForDatagramTransferStateChanged(any(), eq(4), eq(null));
+        mPointingAppController.registerForSatelliteTransmissionUpdates(subId2,
+                callback1, mPhone);
+        mInOrder.verify(mMockSatelliteModemInterface).registerForSatellitePositionInfoChanged(any(),
+                eq(1), eq(null));
+        mInOrder.verify(mMockSatelliteModemInterface).registerForDatagramTransferStateChanged(any(),
+                eq(4), eq(null));
+        mPointingAppController.registerForSatelliteTransmissionUpdates(subId2,
+                callback2, mPhone);
+        mInOrder.verify(mMockSatelliteModemInterface, never())
+                .registerForSatellitePositionInfoChanged(any(), eq(1), eq(null));
+        mInOrder.verify(mMockSatelliteModemInterface, never())
+                .registerForDatagramTransferStateChanged(any(), eq(4), eq(null));
+        mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId1,
+                mResultListener::offer, callback1, mPhone);
+        processAllMessages();
+        //since there are 2 callbacks registered for this sub_id, Handler is not unregistered
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
+        mResultListener.remove();
+        mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId1,
+                mResultListener::offer, callback2, mPhone);
+        mInOrder.verify(mMockSatelliteModemInterface).unregisterForSatellitePositionInfoChanged(
+                any(Handler.class));
+        mInOrder.verify(mMockSatelliteModemInterface).unregisterForDatagramTransferStateChanged(
+                any(Handler.class));
+        mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId2,
+                mResultListener::offer, callback1, mPhone);
+        processAllMessages();
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
+        mResultListener.remove();
+        mInOrder.verify(mMockSatelliteModemInterface, never())
+                .unregisterForSatellitePositionInfoChanged(any(Handler.class));
+        mInOrder.verify(mMockSatelliteModemInterface, never())
+                .unregisterForDatagramTransferStateChanged(any(Handler.class));
+        mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId2,
+                mResultListener::offer, callback2, null);
+        processAllMessages();
+        mInOrder.verify(mMockSatelliteModemInterface).unregisterForSatellitePositionInfoChanged(
+                any(Handler.class));
+        mInOrder.verify(mMockSatelliteModemInterface).unregisterForDatagramTransferStateChanged(
+                any(Handler.class));
+        mInOrder = null;
+    }
+
+    private static void loge(String message) {
+        Log.e(TAG, message);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
index f6ed2e2..edfd610 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
@@ -53,7 +53,9 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.anyVararg;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
@@ -72,7 +74,9 @@
 import android.os.ICancellationSignal;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.os.ResultReceiver;
+import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
 import android.telephony.satellite.ISatelliteDatagramCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
@@ -84,7 +88,9 @@
 import android.telephony.satellite.SatelliteManager.SatelliteException;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.util.Pair;
 
+import com.android.internal.R;
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.IVoidConsumer;
 import com.android.internal.telephony.Phone;
@@ -92,6 +98,7 @@
 import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
 import com.android.internal.telephony.satellite.metrics.ProvisionMetricsStats;
 import com.android.internal.telephony.satellite.metrics.SessionMetricsStats;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 
 import org.junit.After;
 import org.junit.Before;
@@ -107,6 +114,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
@@ -114,11 +122,17 @@
 @TestableLooper.RunWithLooper
 public class SatelliteControllerTest extends TelephonyTest {
     private static final String TAG = "SatelliteControllerTest";
+
+    private static final int EVENT_DEVICE_CONFIG_CHANGED = 29;
+
     private static final long TIMEOUT = 500;
     private static final int SUB_ID = 0;
+    private static final int SUB_ID1 = 1;
     private static final int MAX_BYTES_PER_OUT_GOING_DATAGRAM = 339;
     private static final String TEST_SATELLITE_TOKEN = "TEST_SATELLITE_TOKEN";
     private static final String TEST_NEXT_SATELLITE_TOKEN = "TEST_NEXT_SATELLITE_TOKEN";
+    private static final String[] EMPTY_SATELLITE_SERVICES_SUPPORTED_BY_PROVIDERS_STRING_ARRAY = {};
+    private static final int[] ACTIVE_SUB_IDS = {SUB_ID};
 
     private TestSatelliteController mSatelliteControllerUT;
     private TestSharedPreferences mSharedPreferences;
@@ -130,6 +144,7 @@
     @Mock private ControllerMetricsStats mMockControllerMetricsStats;
     @Mock private ProvisionMetricsStats mMockProvisionMetricsStats;
     @Mock private SessionMetricsStats mMockSessionMetricsStats;
+    @Mock private SubscriptionManagerService mMockSubscriptionManagerService;
     private List<Integer> mIIntegerConsumerResults =  new ArrayList<>();
     @Mock private ISatelliteTransmissionUpdateCallback mStartTransmissionUpdateCallback;
     @Mock private ISatelliteTransmissionUpdateCallback mStopTransmissionUpdateCallback;
@@ -215,6 +230,7 @@
     private ResultReceiver mIsSatelliteEnabledReceiver = new ResultReceiver(null) {
         @Override
         protected void onReceiveResult(int resultCode, Bundle resultData) {
+            logd("mIsSatelliteEnabledReceiver: resultCode=" + resultCode);
             mQueriedIsSatelliteEnabledResultCode = resultCode;
             if (resultCode == SATELLITE_ERROR_NONE) {
                 if (resultData.containsKey(KEY_SATELLITE_ENABLED)) {
@@ -224,7 +240,6 @@
                     mQueriedIsSatelliteEnabled = false;
                 }
             } else {
-                logd("mIsSatelliteEnableReceiver: resultCode=" + resultCode);
                 mQueriedIsSatelliteEnabled = false;
             }
             try {
@@ -343,6 +358,9 @@
         }
     };
 
+    private List<Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener>>
+            mCarrierConfigChangedListenerList = new ArrayList<>();
+    private PersistableBundle mCarrierConfigBundle;
 
     @Before
     public void setUp() throws Exception {
@@ -364,6 +382,25 @@
                 mMockProvisionMetricsStats);
         replaceInstance(SessionMetricsStats.class, "sInstance", null,
                 mMockSessionMetricsStats);
+        replaceInstance(SubscriptionManagerService.class, "sInstance", null,
+                mMockSubscriptionManagerService);
+
+        mContextFixture.putStringArrayResource(
+                R.array.config_satellite_services_supported_by_providers,
+                EMPTY_SATELLITE_SERVICES_SUPPORTED_BY_PROVIDERS_STRING_ARRAY);
+        doReturn(ACTIVE_SUB_IDS).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
+
+        mCarrierConfigBundle = mContextFixture.getCarrierConfigBundle();
+        doReturn(mCarrierConfigBundle)
+                .when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyVararg());
+        doAnswer(invocation -> {
+            Executor executor = invocation.getArgument(0);
+            CarrierConfigManager.CarrierConfigChangeListener listener = invocation.getArgument(1);
+            mCarrierConfigChangedListenerList.add(new Pair<>(executor, listener));
+            return null;
+        }).when(mCarrierConfigManager).registerCarrierConfigChangeListener(
+                any(Executor.class),
+                any(CarrierConfigManager.CarrierConfigChangeListener.class));
 
         mSharedPreferences = new TestSharedPreferences();
         when(mContext.getSharedPreferences(anyString(), anyInt())).thenReturn(mSharedPreferences);
@@ -586,18 +623,39 @@
         processAllMessages();
         verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
 
+        // Successfully enable satellite
+        mIIntegerConsumerResults.clear();
+        mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
+        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_ERROR_NONE);
+        assertTrue(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled);
+        assertEquals(
+                SATELLITE_MODE_ENABLED_TRUE, mSatelliteControllerUT.satelliteModeSettingValue);
+        verify(mMockSatelliteSessionController, times(1)).onSatelliteEnabledStateChanged(eq(true));
+        verify(mMockSatelliteSessionController, times(2)).setDemoMode(eq(false));
+        verify(mMockDatagramController, times(2)).setDemoMode(eq(false));
+        verify(mMockPointingAppController).startPointingUI(eq(false));
+        verify(mMockControllerMetricsStats, times(1)).onSatelliteEnabled();
+        verify(mMockControllerMetricsStats, times(1)).reportServiceEnablementSuccessCount();
+
         // Successfully disable satellite when radio is turned off.
         mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
         setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_ERROR_NONE);
         setRadioPower(false);
         processAllMessages();
+        sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_OFF, null);
+        processAllMessages();
         verifySatelliteEnabled(false, SATELLITE_ERROR_NONE);
         assertTrue(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled);
         assertEquals(
                 SATELLITE_MODE_ENABLED_FALSE, mSatelliteControllerUT.satelliteModeSettingValue);
         verify(mMockSatelliteSessionController, times(2)).onSatelliteEnabledStateChanged(eq(false));
-        verify(mMockSatelliteSessionController, times(2)).setDemoMode(eq(false));
-        verify(mMockDatagramController, times(2)).setDemoMode(eq(false));
+        verify(mMockSatelliteSessionController, times(3)).setDemoMode(eq(false));
+        verify(mMockDatagramController, times(3)).setDemoMode(eq(false));
         verify(mMockControllerMetricsStats, times(1)).onSatelliteDisabled();
 
         // Fail to enable satellite when radio is off.
@@ -615,6 +673,7 @@
 
         // Fail to enable satellite with an error response from modem when radio is on.
         mIIntegerConsumerResults.clear();
+        clearInvocations(mMockPointingAppController);
         mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
         setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_INVALID_MODEM_STATE);
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
@@ -638,14 +697,14 @@
         assertTrue(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled);
         assertEquals(SATELLITE_MODE_ENABLED_TRUE, mSatelliteControllerUT.satelliteModeSettingValue);
         verify(mMockPointingAppController).startPointingUI(eq(false));
-        verify(mMockSatelliteSessionController, times(1)).onSatelliteEnabledStateChanged(eq(true));
-        verify(mMockSatelliteSessionController, times(3)).setDemoMode(eq(false));
-        verify(mMockDatagramController, times(3)).setDemoMode(eq(false));
-        verify(mMockControllerMetricsStats, times(1)).onSatelliteEnabled();
-        verify(mMockControllerMetricsStats, times(1)).reportServiceEnablementSuccessCount();
-        verify(mMockSessionMetricsStats, times(2)).setInitializationResult(anyInt());
-        verify(mMockSessionMetricsStats, times(2)).setRadioTechnology(anyInt());
-        verify(mMockSessionMetricsStats, times(2)).reportSessionMetrics();
+        verify(mMockSatelliteSessionController, times(2)).onSatelliteEnabledStateChanged(eq(true));
+        verify(mMockSatelliteSessionController, times(4)).setDemoMode(eq(false));
+        verify(mMockDatagramController, times(4)).setDemoMode(eq(false));
+        verify(mMockControllerMetricsStats, times(2)).onSatelliteEnabled();
+        verify(mMockControllerMetricsStats, times(2)).reportServiceEnablementSuccessCount();
+        verify(mMockSessionMetricsStats, times(3)).setInitializationResult(anyInt());
+        verify(mMockSessionMetricsStats, times(3)).setRadioTechnology(anyInt());
+        verify(mMockSessionMetricsStats, times(3)).reportSessionMetrics();
 
         // Successfully enable satellite when it is already enabled.
         mIIntegerConsumerResults.clear();
@@ -663,7 +722,7 @@
         assertEquals(SATELLITE_INVALID_ARGUMENTS, (long) mIIntegerConsumerResults.get(0));
         verifySatelliteEnabled(true, SATELLITE_ERROR_NONE);
 
-        // Disable satellite.
+        // Successfully disable satellite.
         mIIntegerConsumerResults.clear();
         setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_ERROR_NONE);
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
@@ -1412,13 +1471,128 @@
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
+    }
 
+    @Test
+    public void testSupportedSatelliteServices() {
+        List<String> satellitePlmnList = mSatelliteControllerUT.getSatellitePlmnList();
+        assertEquals(EMPTY_SATELLITE_SERVICES_SUPPORTED_BY_PROVIDERS_STRING_ARRAY.length,
+                satellitePlmnList.size());
+        List<Integer> supportedSatelliteServices =
+                mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00101");
+        assertTrue(supportedSatelliteServices.isEmpty());
+
+        String[] satellitePlmnArray = {"00101", "00102"};
+        String[] satelliteServicesSupportedByProviderStrArray = {"00101:1,2", "00102:2,3"};
+        int[] expectedSupportedServices1 = {1, 2};
+        int[] expectedSupportedServices2 = {2, 3};
+
+        mContextFixture.putStringArrayResource(
+                R.array.config_satellite_services_supported_by_providers,
+                satelliteServicesSupportedByProviderStrArray);
+        TestSatelliteController testSatelliteController =
+                new TestSatelliteController(mContext, Looper.myLooper());
+
+        satellitePlmnList = testSatelliteController.getSatellitePlmnList();
+        assertTrue(Arrays.equals(satellitePlmnArray, satellitePlmnList.stream().toArray()));
+
+        supportedSatelliteServices =
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00101");
+        assertNotNull(supportedSatelliteServices);
+        assertTrue(Arrays.equals(expectedSupportedServices1,
+                supportedSatelliteServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+
+        supportedSatelliteServices =
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00102");
+        assertNotNull(supportedSatelliteServices);
+        assertTrue(Arrays.equals(expectedSupportedServices2,
+                supportedSatelliteServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+
+        // Carrier config changed
+        int[] expectedSupportedServices3 = {2};
+        int[] supportedServices = {1, 3};
+        PersistableBundle carrierSupportedSatelliteServicesPerProvider = new PersistableBundle();
+        carrierSupportedSatelliteServicesPerProvider.putIntArray(
+                "00102", expectedSupportedServices3);
+        carrierSupportedSatelliteServicesPerProvider.putIntArray("00103", supportedServices);
+        mCarrierConfigBundle.putPersistableBundle(CarrierConfigManager
+                        .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
+                carrierSupportedSatelliteServicesPerProvider);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+
+        supportedSatelliteServices =
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00101");
+        assertNotNull(supportedSatelliteServices);
+        assertTrue(Arrays.equals(expectedSupportedServices1,
+                supportedSatelliteServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+
+        supportedSatelliteServices =
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00102");
+        assertNotNull(supportedSatelliteServices);
+        assertTrue(Arrays.equals(expectedSupportedServices3,
+                supportedSatelliteServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+
+        supportedSatelliteServices =
+                mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00103");
+        assertTrue(supportedSatelliteServices.isEmpty());
+
+        // Subscriptions changed
+        int[] newActiveSubIds = {SUB_ID1};
+        doReturn(newActiveSubIds).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+
+        supportedSatelliteServices =
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00101");
+        assertTrue(supportedSatelliteServices.isEmpty());
+        supportedSatelliteServices =
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00102");
+        assertTrue(supportedSatelliteServices.isEmpty());
+
+        supportedSatelliteServices =
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID1, "00101");
+        assertNotNull(supportedSatelliteServices);
+        assertTrue(Arrays.equals(expectedSupportedServices1,
+                supportedSatelliteServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+
+        supportedSatelliteServices =
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID1, "00102");
+        assertNotNull(supportedSatelliteServices);
+        assertTrue(Arrays.equals(expectedSupportedServices3,
+                supportedSatelliteServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+
+        supportedSatelliteServices =
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID1, "00103");
+        assertTrue(supportedSatelliteServices.isEmpty());
     }
 
     private void resetSatelliteControllerUTEnabledState() {
         logd("resetSatelliteControllerUTEnabledState");
         setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RADIO_NOT_AVAILABLE);
-        doReturn(true).when(mMockSatelliteModemInterface)
+        doNothing().when(mMockSatelliteModemInterface)
                 .setSatelliteServicePackageName(anyString());
         mSatelliteControllerUT.setSatelliteServicePackageName("TestSatelliteService");
         processAllMessages();
@@ -1438,7 +1612,7 @@
 
         // Reset all cached states
         setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RADIO_NOT_AVAILABLE);
-        doReturn(true).when(mMockSatelliteModemInterface)
+        doNothing().when(mMockSatelliteModemInterface)
                 .setSatelliteServicePackageName(anyString());
         mSatelliteControllerUT.setSatelliteServicePackageName("TestSatelliteService");
         processAllMessages();
@@ -1455,7 +1629,8 @@
 
     private void resetSatelliteControllerUTToOffAndProvisionedState() {
         resetSatelliteControllerUTToSupportedAndProvisionedState();
-        sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_OFF, null);
+        // Clean up pending resources and move satellite controller to OFF state.
+        sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_UNAVAILABLE, null);
         processAllMessages();
         verifySatelliteEnabled(false, SATELLITE_ERROR_NONE);
     }
@@ -1568,6 +1743,9 @@
         SatelliteException exception = (error == SATELLITE_ERROR_NONE)
                 ? null : new SatelliteException(error);
         doAnswer(invocation -> {
+            if (exception == null && !enabled) {
+                sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_OFF, null);
+            }
             Message message = (Message) invocation.getArguments()[2];
             AsyncResult.forMessage(message, null, exception);
             message.sendToTarget();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
index 418d0aa..5e11297 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
@@ -36,6 +36,7 @@
 import android.telecom.Call;
 import android.telecom.Connection;
 import android.telephony.BinderCacheManager;
+import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.ims.RegistrationManager;
@@ -96,6 +97,14 @@
         when(mMockContext.getResources()).thenReturn(mResources);
         when(mResources.getString(com.android.internal.R.string.config_satellite_service_package))
                 .thenReturn("");
+        when(mMockContext.getSystemServiceName(CarrierConfigManager.class))
+                .thenReturn("CarrierConfigManager");
+        when(mMockContext.getSystemService(CarrierConfigManager.class))
+                .thenReturn(mCarrierConfigManager);
+        when(mMockContext.getSystemServiceName(SubscriptionManager.class))
+                .thenReturn("SubscriptionManager");
+        when(mMockContext.getSystemService(SubscriptionManager.class))
+                .thenReturn(mSubscriptionManager);
         mTestSatelliteController = new TestSatelliteController(mMockContext,
                 Looper.myLooper());
         mTestImsManager = new TestImsManager(
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteServiceUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteServiceUtilsTest.java
new file mode 100644
index 0000000..ba1fb9e
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteServiceUtilsTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2023 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.internal.telephony.satellite;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.PersistableBundle;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class SatelliteServiceUtilsTest extends TelephonyTest {
+    private static final String TAG = "SatelliteServiceUtilsTest";
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
+        logd(TAG + " Setup!");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        logd(TAG + " tearDown");
+        super.tearDown();
+    }
+
+    @Test
+    public void testParseSupportedSatelliteServicesFromStringArray() {
+        // Parse correct format input string
+        int[] expectedServices1 = {2, 3};
+        int[] expectedServices2 = {3};
+        String[] supportedServicesStrArr1 = {"10011:2,3", "10112:3"};
+        Map<String, Set<Integer>> supportedServiceMap =
+                SatelliteServiceUtils.parseSupportedSatelliteServices(supportedServicesStrArr1);
+
+        assertTrue(supportedServiceMap.containsKey("10011"));
+        Set<Integer> supportedServices = supportedServiceMap.get("10011");
+        assertTrue(Arrays.equals(expectedServices1,
+                supportedServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+
+        assertTrue(supportedServiceMap.containsKey("10112"));
+        supportedServices = supportedServiceMap.get("10112");
+        assertTrue(Arrays.equals(expectedServices2,
+                supportedServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+
+        // Parse correct mixed with incorrect format input string
+        String[] supportedServicesStrArr2 = {"10011:2,3,1xy", "10112:3,70", "10012:"};
+        supportedServiceMap = SatelliteServiceUtils.parseSupportedSatelliteServices(
+                supportedServicesStrArr2);
+
+        assertTrue(supportedServiceMap.containsKey("10011"));
+        supportedServices = supportedServiceMap.get("10011");
+        assertTrue(Arrays.equals(expectedServices1,
+                supportedServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+
+        assertTrue(supportedServiceMap.containsKey("10112"));
+        supportedServices = supportedServiceMap.get("10112");
+        assertTrue(Arrays.equals(expectedServices2,
+                supportedServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+
+        assertTrue(supportedServiceMap.containsKey("10012"));
+        assertTrue(supportedServiceMap.get("10012").isEmpty());
+
+        // Parse an empty input string
+        String[] supportedServicesStrArr3 = {};
+        supportedServiceMap = SatelliteServiceUtils.parseSupportedSatelliteServices(
+                supportedServicesStrArr3);
+        assertTrue(supportedServiceMap.isEmpty());
+    }
+
+    @Test
+    public void testParseSupportedSatelliteServicesFromPersistableBundle() {
+        PersistableBundle supportedServicesBundle = new PersistableBundle();
+        String plmn1 = "10101";
+        String plmn2 = "10102";
+        String plmn3 = "10103";
+        int[] supportedServicesForPlmn1 = {1, 2, 3};
+        int[] supportedServicesForPlmn2 = {3, 4, 100};
+        int[] expectedServicesForPlmn1 = {1, 2, 3};
+        int[] expectedServicesForPlmn2 = {3, 4};
+
+        // Parse an empty bundle
+        Map<String, Set<Integer>> supportedServiceMap =
+                SatelliteServiceUtils.parseSupportedSatelliteServices(supportedServicesBundle);
+        assertTrue(supportedServiceMap.isEmpty());
+
+        // Add some more fields
+        supportedServicesBundle.putIntArray(plmn1, supportedServicesForPlmn1);
+        supportedServicesBundle.putIntArray(plmn2, supportedServicesForPlmn2);
+        supportedServicesBundle.putIntArray(plmn3, new int[0]);
+
+        supportedServiceMap =
+                SatelliteServiceUtils.parseSupportedSatelliteServices(supportedServicesBundle);
+        assertEquals(3, supportedServiceMap.size());
+
+        assertTrue(supportedServiceMap.containsKey(plmn1));
+        Set<Integer> supportedServices = supportedServiceMap.get(plmn1);
+        assertTrue(Arrays.equals(expectedServicesForPlmn1,
+                supportedServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+
+        assertTrue(supportedServiceMap.containsKey(plmn2));
+        supportedServices = supportedServiceMap.get(plmn2);
+        assertTrue(Arrays.equals(expectedServicesForPlmn2,
+                supportedServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+
+        assertTrue(supportedServiceMap.containsKey(plmn3));
+        supportedServices = supportedServiceMap.get(plmn3);
+        assertTrue(supportedServices.isEmpty());
+    }
+
+    @Test
+    public void testMergeSupportedSatelliteServices() {
+        String plmn1 = "00101";
+        String plmn2 = "00102";
+        String plmn3 = "00103";
+
+        Integer[] providerSupportedServicesForPlmn1 = {1, 2, 3};
+        Integer[] providerSupportedServicesForPlmn2 = {3, 4};
+        Map<String, Set<Integer>> providerSupportedServicesMap = new HashMap<>();
+        providerSupportedServicesMap.put(
+                plmn1, new HashSet<>(Arrays.asList(providerSupportedServicesForPlmn1)));
+        providerSupportedServicesMap.put(
+                plmn2, new HashSet<>(Arrays.asList(providerSupportedServicesForPlmn2)));
+
+        Integer[] carrierSupportedServicesForPlmn2 = {3};
+        Integer[] carrierSupportedServicesForPlmn3 = {1, 3, 4};
+        Map<String, Set<Integer>> carrierSupportedServicesMap = new HashMap<>();
+        carrierSupportedServicesMap.put(
+                plmn2, new HashSet<>(Arrays.asList(carrierSupportedServicesForPlmn2)));
+        carrierSupportedServicesMap.put(
+                plmn3, new HashSet<>(Arrays.asList(carrierSupportedServicesForPlmn3)));
+
+        // {@code plmn1} is present in only provider support services.
+        int[] expectedSupportedServicesForPlmn1 = {1, 2, 3};
+        // Intersection of {3,4} and {3}.
+        int[] expectedSupportedServicesForPlmn2 = {3};
+        Map<String, Set<Integer>> supportedServicesMap =
+                SatelliteServiceUtils.mergeSupportedSatelliteServices(
+                        providerSupportedServicesMap, carrierSupportedServicesMap);
+
+        assertEquals(2, supportedServicesMap.size());
+        assertTrue(supportedServicesMap.containsKey(plmn1));
+        assertTrue(supportedServicesMap.containsKey(plmn2));
+        assertFalse(supportedServicesMap.containsKey(plmn3));
+        assertTrue(Arrays.equals(expectedSupportedServicesForPlmn1,
+                supportedServicesMap.get(plmn1).stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+        assertTrue(Arrays.equals(expectedSupportedServicesForPlmn2,
+                supportedServicesMap.get(plmn2).stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
index 9898353..0358809 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
@@ -269,6 +269,8 @@
 
         private final List<String> mAllColumns;
 
+        private boolean mDatabaseChanged;
+
         SubscriptionProvider() {
             mAllColumns = SimInfo.getAllColumns();
         }
@@ -378,7 +380,17 @@
 
         @Override
         public Bundle call(String method, @Nullable String args, @Nullable Bundle bundle) {
-            return new Bundle();
+            Bundle result = new Bundle();
+            if (method.equals(SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME)) {
+                result.putBoolean(
+                        SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_DATABASE_UPDATED,
+                        mDatabaseChanged);
+            }
+            return result;
+        }
+
+        public void setRestoreDatabaseChanged(boolean changed) {
+            mDatabaseChanged = changed;
         }
     }
 
@@ -422,7 +434,7 @@
                 .that(mDatabaseManagerUT.getSubscriptionInfoInternal(subId)).isEqualTo(subInfo);
 
         // Load subscription info from the database.
-        mDatabaseManagerUT.reloadDatabase();
+        mDatabaseManagerUT.reloadDatabaseSync();
         processAllMessages();
 
         // Verify the database value is same as the inserted one.
diff --git a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
index 06dd17c..aea7965 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
@@ -69,8 +69,10 @@
 import android.app.AppOpsManager;
 import android.app.PropertyInvalidatedCache;
 import android.compat.testing.PlatformCompatChangeRule;
+import android.content.ContentValues;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -174,6 +176,8 @@
         mContextFixture.putBooleanResource(com.android.internal.R.bool
                 .config_subscription_database_async_update, true);
         mContextFixture.putIntArrayResource(com.android.internal.R.array.sim_colors, new int[0]);
+        mContextFixture.putResource(com.android.internal.R.string.default_card_name,
+                FAKE_DEFAULT_CARD_NAME);
 
         mContextFixture.addSystemFeature(PackageManager.FEATURE_TELEPHONY_EUICC);
         setupMocksForTelephonyPermissions(Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
@@ -829,6 +833,8 @@
         doReturn(result).when(mEuiccController).blockingGetEuiccProfileInfoList(eq(2));
         doReturn(TelephonyManager.INVALID_PORT_INDEX).when(mUiccSlot)
                 .getPortIndexFromIccId(anyString());
+        doReturn(FAKE_ICCID1).when(mUiccController).convertToCardString(eq(1));
+        doReturn(FAKE_ICCID2).when(mUiccController).convertToCardString(eq(2));
 
         mSubscriptionManagerServiceUT.updateEmbeddedSubscriptions(List.of(1, 2), null);
         processAllMessages();
@@ -849,6 +855,9 @@
         assertThat(subInfo.isEmbedded()).isTrue();
         assertThat(subInfo.isRemovableEmbedded()).isFalse();
         assertThat(subInfo.getNativeAccessRules()).isEqualTo(FAKE_NATIVE_ACCESS_RULES1);
+        // Downloaded esim profile should contain proper cardId
+        assertThat(subInfo.getCardId()).isEqualTo(1);
+        assertThat(subInfo.getCardString()).isEqualTo(FAKE_ICCID1);
 
         subInfo = mSubscriptionManagerServiceUT.getSubscriptionInfoInternal(2);
         assertThat(subInfo.getSubscriptionId()).isEqualTo(2);
@@ -865,6 +874,9 @@
         assertThat(subInfo.isEmbedded()).isTrue();
         assertThat(subInfo.isRemovableEmbedded()).isFalse();
         assertThat(subInfo.getNativeAccessRules()).isEqualTo(FAKE_NATIVE_ACCESS_RULES2);
+        // Downloaded esim profile should contain proper cardId
+        assertThat(subInfo.getCardId()).isEqualTo(2);
+        assertThat(subInfo.getCardString()).isEqualTo(FAKE_ICCID2);
     }
 
     @Test
@@ -1084,6 +1096,19 @@
     }
 
     @Test
+    public void testGetSubscriptionUserHandleUnknownSubscription() {
+        mContextFixture.addCallingOrSelfPermission(
+                Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
+
+        // getSubscriptionUserHandle() returns null when subscription is not available on the device
+        assertThat(mSubscriptionManagerServiceUT.getSubscriptionUserHandle(10))
+                .isEqualTo(null);
+
+        mContextFixture.removeCallingOrSelfPermission(
+                Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
+    }
+
+    @Test
     public void testIsSubscriptionAssociatedWithUser() {
         insertSubscription(FAKE_SUBSCRIPTION_INFO1);
 
@@ -1891,12 +1916,16 @@
         assertThat(mSubscriptionManagerServiceUT.getSlotIndex(1)).isEqualTo(0);
         assertThat(mSubscriptionManagerServiceUT.getPhoneId(1)).isEqualTo(0);
 
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerServiceUT
+                .getSubscriptionInfoInternal(1);
+        assertThat(subInfo.getDisplayName()).isEqualTo("CARD 1");
+
         mSubscriptionManagerServiceUT.setCarrierId(1, FAKE_CARRIER_ID1);
         mSubscriptionManagerServiceUT.setDisplayNameUsingSrc(FAKE_CARRIER_NAME1, 1,
                 SubscriptionManager.NAME_SOURCE_SIM_SPN);
         mSubscriptionManagerServiceUT.setCarrierName(1, FAKE_CARRIER_NAME1);
 
-        SubscriptionInfoInternal subInfo = mSubscriptionManagerServiceUT
+        subInfo = mSubscriptionManagerServiceUT
                 .getSubscriptionInfoInternal(1);
         assertThat(subInfo.getSubscriptionId()).isEqualTo(1);
         assertThat(subInfo.getSimSlotIndex()).isEqualTo(0);
@@ -2314,9 +2343,6 @@
 
     @Test
     public void testInactiveSimInserted() {
-        mContextFixture.putResource(com.android.internal.R.string.default_card_name,
-                FAKE_DEFAULT_CARD_NAME);
-
         doReturn(0).when(mUiccSlot).getPortIndexFromIccId(eq(FAKE_ICCID1));
 
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
@@ -2333,16 +2359,40 @@
     }
 
     @Test
-    public void testRestoreAllSimSpecificSettingsFromBackup() {
+    public void testRestoreAllSimSpecificSettingsFromBackup() throws Exception {
         assertThrows(SecurityException.class, ()
                 -> mSubscriptionManagerServiceUT.restoreAllSimSpecificSettingsFromBackup(
                         new byte[0]));
+        insertSubscription(FAKE_SUBSCRIPTION_INFO1);
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
 
-        // TODO: Briefly copy the logic from TelephonyProvider to
-        //  SubscriptionDatabaseManagerTest.SubscriptionProvider
+
+        // getSubscriptionDatabaseManager().setWifiCallingEnabled(1, 0);
+
+        // Simulate restoration altered the database directly.
+        ContentValues cvs = new ContentValues();
+        cvs.put(SimInfo.COLUMN_WFC_IMS_ENABLED, 0);
+        mSubscriptionProvider.update(Uri.withAppendedPath(SimInfo.CONTENT_URI, "1"), cvs, null,
+                null);
+
+        // Setting this to false to prevent database reload.
+        mSubscriptionProvider.setRestoreDatabaseChanged(false);
         mSubscriptionManagerServiceUT.restoreAllSimSpecificSettingsFromBackup(
                 new byte[0]);
+
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerServiceUT
+                .getSubscriptionInfoInternal(1);
+        // Since reload didn't happen, WFC should remains enabled.
+        assertThat(subInfo.getWifiCallingEnabled()).isEqualTo(1);
+
+        // Now the database reload should happen
+        mSubscriptionProvider.setRestoreDatabaseChanged(true);
+        mSubscriptionManagerServiceUT.restoreAllSimSpecificSettingsFromBackup(
+                new byte[0]);
+
+        subInfo = mSubscriptionManagerServiceUT.getSubscriptionInfoInternal(1);
+        // Since reload didn't happen, WFC should remains enabled.
+        assertThat(subInfo.getWifiCallingEnabled()).isEqualTo(0);
     }
 
     @Test
@@ -2494,7 +2544,7 @@
                 .getSubscriptionInfoInternal(1);
         assertThat(subInfo.getSubscriptionId()).isEqualTo(1);
         assertThat(subInfo.getIccId()).isEqualTo(FAKE_ICCID1);
-        assertThat(subInfo.getDisplayName()).isEqualTo("");
+        assertThat(subInfo.getDisplayName()).isEqualTo("CARD 1");
         assertThat(subInfo.getDisplayNameSource()).isEqualTo(
                 SubscriptionManager.NAME_SOURCE_UNKNOWN);
         assertThat(subInfo.getMcc()).isEqualTo("");
@@ -2506,7 +2556,7 @@
         subInfo = mSubscriptionManagerServiceUT.getSubscriptionInfoInternal(2);
         assertThat(subInfo.getSubscriptionId()).isEqualTo(2);
         assertThat(subInfo.getIccId()).isEqualTo(FAKE_ICCID2);
-        assertThat(subInfo.getDisplayName()).isEqualTo("");
+        assertThat(subInfo.getDisplayName()).isEqualTo("CARD 2");
         assertThat(subInfo.getDisplayNameSource()).isEqualTo(
                 SubscriptionManager.NAME_SOURCE_UNKNOWN);
         assertThat(subInfo.getMcc()).isEqualTo(FAKE_MCC2);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java
index 143d7c9..15fb729 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java
@@ -570,7 +570,7 @@
             public Void answer(InvocationOnMock invocation) throws Throwable {
                 currentFileId.set((String) invocation.getArguments()[6]);
                 Message message = (Message) invocation.getArguments()[8];
-                AsyncResult ar = new AsyncResult(null, new int[]{2}, null);
+                AsyncResult ar = new AsyncResult(null, new IccIoResult(0x90, 0x00, ""), null);
                 message.obj = ar;
                 message.sendToTarget();
                 return null;
@@ -668,7 +668,7 @@
             public Void answer(InvocationOnMock invocation) throws Throwable {
                 currentFileId.set((String) invocation.getArguments()[6]);
                 Message message = (Message) invocation.getArguments()[8];
-                AsyncResult ar = new AsyncResult(null, new int[]{2}, null);
+                AsyncResult ar = new AsyncResult(null, new IccIoResult(0x90, 0x00, ""), null);
                 message.obj = ar;
                 message.sendToTarget();
                 return null;