Merge "Support handling reconnect to QualifiedNetworkType" into main
diff --git a/OWNERS b/OWNERS
index 7e57200..ff1a04e 100644
--- a/OWNERS
+++ b/OWNERS
@@ -18,4 +18,4 @@
 xiaotonj@google.com
 
 # Domain Selection code is co-owned, adding additional owners for this code
-per-file EmergencyStateTracker*=hwangoo@google.com,forestchoi@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com
+per-file EmergencyStateTracker*=hwangoo@google.com,forestchoi@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,jdyou@google.com
diff --git a/flags/calling.aconfig b/flags/calling.aconfig
index 82f932b..e67ebc6 100644
--- a/flags/calling.aconfig
+++ b/flags/calling.aconfig
@@ -5,4 +5,11 @@
   namespace: "telephony"
   description: "APIs that are used to notify simultaneous calling changes to other applications."
   bug: "297446980"
+}
+
+flag {
+  name: "show_call_fail_notification_for_2g_toggle"
+  namespace: "telephony"
+  description: "Used in DisconnectCause and TelephonyConnection if a non-emergency call fails on a device with no 2G, to guard whether a user can see an updated error message reminding the 2G is disabled and potentially disrupting their call connectivity"
+  bug: "300142897"
 }
\ No newline at end of file
diff --git a/flags/misc.aconfig b/flags/misc.aconfig
index dff6426..e67dabe 100644
--- a/flags/misc.aconfig
+++ b/flags/misc.aconfig
@@ -70,3 +70,13 @@
     description: "Allows applications to launch Reset Mobile Network Settings page in Settings app."
     bug:"271921464"
 }
+
+flag {
+    name: "fix_crash_on_getting_config_when_phone_is_gone"
+    namespace: "telephony"
+    description: "Fix VCN crash when calling CarrierConfigManager.getConfigForSubId while phone process has gone."
+    bug:"319791612"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/flags/network.aconfig b/flags/network.aconfig
index e4ae5ee..c0394e8 100644
--- a/flags/network.aconfig
+++ b/flags/network.aconfig
@@ -53,5 +53,12 @@
   name: "support_nr_sa_rrc_idle"
   namespace: "telephony"
   description: "Support RRC idle for NR SA."
-  bug: "301467052"
+  bug: "298233308"
+}
+
+flag {
+  name: "network_registration_info_reject_cause"
+  namespace: "telephony"
+  description: "Elevate NRI#getRejectCause from System to Public"
+  bug: "239730435"
 }
diff --git a/flags/subscription.aconfig b/flags/subscription.aconfig
index d2c36d6..cebedd5 100644
--- a/flags/subscription.aconfig
+++ b/flags/subscription.aconfig
@@ -26,4 +26,18 @@
   namespace: "telephony"
   description: "Support emergency call only for data only cellular service."
   bug: "296097429"
+}
+
+flag {
+  name: "support_psim_to_esim_conversion"
+  namespace: "telephony"
+  description: "Support the psim to esim conversion."
+  bug: "315073761"
+}
+
+flag {
+  name: "subscription_user_association_query"
+  namespace: "telephony"
+  description: "Supports querying if a subscription is associated with the caller"
+  bug: "325045841"
 }
\ No newline at end of file
diff --git a/flags/telephony.aconfig b/flags/telephony.aconfig
index c6fd2c4..d59b249 100644
--- a/flags/telephony.aconfig
+++ b/flags/telephony.aconfig
@@ -19,4 +19,11 @@
     namespace: "telephony"
     description: "This flag controls the order of the binder to prevent deadlock in system_server"
     bug: "315973270"
+}
+
+flag {
+    name: "prevent_invocation_repeat_of_ril_call_when_device_does_not_support_voice"
+    namespace: "telephony"
+    description: "This flag prevents repeat invocation of call related APIs in RIL when the device is not voice capable"
+    bug: "290833783"
 }
\ No newline at end of file
diff --git a/flags/uicc.aconfig b/flags/uicc.aconfig
index 5b74d1a..c1b860f 100644
--- a/flags/uicc.aconfig
+++ b/flags/uicc.aconfig
@@ -18,10 +18,15 @@
     description: "This flag controls the visibility of the getCarrierRestrictionStatus in carrierRestrictionRules class."
     bug:"313553044"
 }
-
 flag {
     name: "carrier_restriction_rules_enhancement"
     namespace: "telephony"
     description: "This flag controls the new enhancements to the existing carrier restrictions rules"
     bug:"317226653"
-}
\ No newline at end of file
+}
+flag {
+    name: "esim_available_memory"
+    namespace: "telephony"
+    description: "This flag controls eSIM available memory feature."
+    bug:"318348580"
+}
diff --git a/proto/src/persist_atoms.proto b/proto/src/persist_atoms.proto
index 02a4125..c07c797 100644
--- a/proto/src/persist_atoms.proto
+++ b/proto/src/persist_atoms.proto
@@ -421,6 +421,7 @@
 }
 
 message ImsRegistrationStats {
+    reserved 16;
     optional int32 carrier_id = 1;
     optional int32 sim_slot_index = 2;
     optional int32 rat = 3;
@@ -437,7 +438,7 @@
     optional int64 registering_millis = 13;
     optional int64 unregistered_millis = 14;
     optional bool is_iwlan_cross_sim = 15;
-    optional int64 registered_times = 16;
+    optional int32 registered_times = 17;
 
     // Internal use only
     optional int64 last_used_millis = 10001;
diff --git a/src/java/com/android/internal/telephony/CallTracker.java b/src/java/com/android/internal/telephony/CallTracker.java
index 38c6672..5e617f9 100644
--- a/src/java/com/android/internal/telephony/CallTracker.java
+++ b/src/java/com/android/internal/telephony/CallTracker.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.AsyncResult;
@@ -25,8 +26,11 @@
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
+import com.android.internal.telephony.flags.FeatureFlags;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -55,6 +59,9 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     protected boolean mNumberConverted = false;
+
+    protected final @NonNull FeatureFlags mFeatureFlags;
+
     private final int VALID_COMPARE_LENGTH   = 3;
 
     //***** Events
@@ -77,7 +84,8 @@
     protected static final int EVENT_THREE_WAY_DIAL_BLANK_FLASH    = 20;
 
     @UnsupportedAppUsage
-    public CallTracker() {
+    public CallTracker(FeatureFlags featureFlags) {
+        mFeatureFlags = featureFlags;
     }
 
     protected void pollCallsWhenSafe() {
@@ -91,6 +99,14 @@
 
     protected void
     pollCallsAfterDelay() {
+        if (mFeatureFlags.preventInvocationRepeatOfRilCallWhenDeviceDoesNotSupportVoice()) {
+            if (!mCi.getHalVersion(TelephonyManager.HAL_SERVICE_VOICE)
+                    .greaterOrEqual(RIL.RADIO_HAL_VERSION_1_4)) {
+                log("Skip polling because HAL_SERVICE_VOICE < RADIO_HAL_VERSION_1.4");
+                return;
+            }
+        }
+
         Message msg = obtainMessage();
 
         msg.what = EVENT_REPOLL_AFTER_DELAY;
diff --git a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
index d76ee19..5517bc6 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
@@ -47,6 +47,7 @@
 import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
 import com.android.internal.telephony.domainselection.DomainSelectionResolver;
 import com.android.internal.telephony.emergency.EmergencyStateTracker;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.telephony.Rlog;
 
@@ -155,7 +156,9 @@
 
     //***** Constructors
 
-    public GsmCdmaCallTracker (GsmCdmaPhone phone) {
+    public GsmCdmaCallTracker(GsmCdmaPhone phone, FeatureFlags featureFlags) {
+        super(featureFlags);
+
         this.mPhone = phone;
         mCi = phone.mCi;
         mCi.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null);
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 7e2143a..aca759b 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -16,10 +16,7 @@
 
 package com.android.internal.telephony;
 
-import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS;
-import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS_PS;
 import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
-import static android.telephony.NetworkRegistrationInfo.DOMAIN_UNKNOWN;
 
 import static com.android.internal.telephony.CommandException.Error.GENERIC_FAILURE;
 import static com.android.internal.telephony.CommandException.Error.SIM_BUSY;
@@ -75,7 +72,6 @@
 import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.Annotation.DataActivityType;
 import android.telephony.Annotation.RadioPowerState;
-import android.telephony.AnomalyReporter;
 import android.telephony.BarringInfo;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellBroadcastIdRange;
@@ -96,6 +92,7 @@
 import android.telephony.UssdResponse;
 import android.telephony.ims.ImsCallProfile;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 
@@ -119,6 +116,7 @@
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.metrics.VoiceCallSessionStats;
 import com.android.internal.telephony.security.CellularIdentifierDisclosureNotifier;
+import com.android.internal.telephony.security.CellularNetworkSecuritySafetySource;
 import com.android.internal.telephony.security.NullCipherNotifier;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService.SubscriptionManagerServiceCallback;
@@ -150,7 +148,6 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
-import java.util.UUID;
 import java.util.function.Consumer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -304,15 +301,24 @@
     private final SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionsChangedListener;
     private final CallWaitingController mCallWaitingController;
 
+    private CellularNetworkSecuritySafetySource mSafetySource;
     private CellularIdentifierDisclosureNotifier mIdentifierDisclosureNotifier;
     private NullCipherNotifier mNullCipherNotifier;
 
+    /**
+     * Temporary placeholder variables until b/312788638 is resolved, whereupon these should be
+     * ported to TelephonyManager.
+     */
     // Set via Carrier Config
-    private boolean mIsN1ModeAllowedByCarrier = true;
+    private static final Integer N1_MODE_DISALLOWED_REASON_CARRIER = 1;
     // Set via a call to the method on Phone; the only caller is IMS, and all of this code will
     // need to be updated to a voting mechanism (...enabled for reason...) if additional callers
     // are desired.
-    private boolean mIsN1ModeAllowedByIms = true;
+    private static final Integer N1_MODE_DISALLOWED_REASON_IMS = 2;
+
+    // Set of use callers/reasons why N1 Mode is disallowed. If the set is empty, it's allowed.
+    private final Set<Integer> mN1ModeDisallowedReasons = new ArraySet<>();
+
     // If this value is null, then the modem value is unknown. If a caller explicitly sets the
     // N1 mode, this value will be initialized before any attempt to set the value in the modem.
     private Boolean mModemN1Mode = null;
@@ -464,7 +470,7 @@
         }
 
         mCT = mTelephonyComponentFactory.inject(GsmCdmaCallTracker.class.getName())
-                .makeGsmCdmaCallTracker(this);
+                .makeGsmCdmaCallTracker(this, mFeatureFlags);
         mIccPhoneBookIntManager = mTelephonyComponentFactory
                 .inject(IccPhoneBookInterfaceManager.class.getName())
                 .makeIccPhoneBookInterfaceManager(this);
@@ -525,6 +531,12 @@
 
         mCi.registerForImeiMappingChanged(this, EVENT_IMEI_MAPPING_CHANGED, null);
 
+        if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()
+                || mFeatureFlags.enableModemCipherTransparencyUnsolEvents()) {
+            mSafetySource =
+                    mTelephonyComponentFactory.makeCellularNetworkSecuritySafetySource(mContext);
+        }
+
         if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()) {
             logi(
                     "enable_identifier_disclosure_transparency_unsol_events is on. Registering for "
@@ -533,7 +545,7 @@
             mIdentifierDisclosureNotifier =
                     mTelephonyComponentFactory
                             .inject(CellularIdentifierDisclosureNotifier.class.getName())
-                            .makeIdentifierDisclosureNotifier();
+                            .makeIdentifierDisclosureNotifier(mSafetySource);
             mCi.registerForCellularIdentifierDisclosures(
                     this, EVENT_CELL_IDENTIFIER_DISCLOSURE, null);
         }
@@ -1486,24 +1498,6 @@
                 && (isWpsCall ? allowWpsOverIms : true);
 
         Bundle extras = dialArgs.intentExtras;
-        if (extras != null && extras.containsKey(PhoneConstants.EXTRA_COMPARE_DOMAIN)) {
-            int domain = extras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN);
-            if (!isEmergency && (!isMmiCode || isPotentialUssdCode)) {
-                if ((domain == DOMAIN_PS && !useImsForCall)
-                        || (domain == DOMAIN_CS && useImsForCall)
-                        || domain == DOMAIN_UNKNOWN || domain == DOMAIN_CS_PS) {
-                    loge("[Anomaly] legacy-useImsForCall:" + useImsForCall
-                            + ", NCDS-domain:" + domain);
-
-                    AnomalyReporter.reportAnomaly(
-                            UUID.fromString("bfae6c2e-ca2f-4121-b167-9cad26a3b353"),
-                            "Domain selection results don't match. useImsForCall:"
-                                    + useImsForCall + ", NCDS-domain:" + domain);
-                }
-            }
-            extras.remove(PhoneConstants.EXTRA_COMPARE_DOMAIN);
-        }
-
         // Only when the domain selection service is supported, EXTRA_DIAL_DOMAIN extra shall exist.
         if (extras != null && extras.containsKey(PhoneConstants.EXTRA_DIAL_DOMAIN)) {
             int domain = extras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN);
@@ -2426,7 +2420,11 @@
             // This might be called by IMS on another thread, so to avoid the requirement to
             // lock, post it through the handler.
             post(() -> {
-                mIsN1ModeAllowedByIms = enable;
+                if (enable) {
+                    mN1ModeDisallowedReasons.remove(N1_MODE_DISALLOWED_REASON_IMS);
+                } else {
+                    mN1ModeDisallowedReasons.add(N1_MODE_DISALLOWED_REASON_IMS);
+                }
                 if (mModemN1Mode == null) {
                     mCi.isN1ModeEnabled(obtainMessage(EVENT_GET_N1_MODE_ENABLED_DONE, result));
                 } else {
@@ -2440,7 +2438,7 @@
 
     /** Only called on the handler thread. */
     private void maybeUpdateModemN1Mode(@Nullable Message result) {
-        final boolean wantN1Enabled = mIsN1ModeAllowedByCarrier && mIsN1ModeAllowedByIms;
+        final boolean wantN1Enabled = mN1ModeDisallowedReasons.isEmpty();
 
         logd("N1 Mode: isModemN1Enabled=" + mModemN1Mode + ", wantN1Enabled=" + wantN1Enabled);
 
@@ -2466,8 +2464,15 @@
         final int[] supportedNrModes = b.getIntArray(
                 CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY);
 
-        mIsN1ModeAllowedByCarrier = ArrayUtils.contains(
-                supportedNrModes, CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA);
+
+        if (ArrayUtils.contains(
+                supportedNrModes,
+                CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA)) {
+            mN1ModeDisallowedReasons.remove(N1_MODE_DISALLOWED_REASON_CARRIER);
+        } else {
+            mN1ModeDisallowedReasons.add(N1_MODE_DISALLOWED_REASON_CARRIER);
+        }
+
         if (mModemN1Mode == null) {
             mCi.isN1ModeEnabled(obtainMessage(EVENT_GET_N1_MODE_ENABLED_DONE));
         } else {
@@ -2602,7 +2607,7 @@
             Bundle extras = new Bundle();
             extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
 
-            final TelecomManager telecomManager = TelecomManager.from(mContext);
+            final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
             telecomManager.placeCall(
                     Uri.fromParts(PhoneAccount.SCHEME_TEL, cfNumber, null), extras);
 
@@ -2863,7 +2868,7 @@
             Bundle extras = new Bundle();
             extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
 
-            final TelecomManager telecomManager = TelecomManager.from(mContext);
+            final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
             telecomManager.placeCall(
                     Uri.fromParts(PhoneAccount.SCHEME_TEL, cwPrefix, null), extras);
 
@@ -3717,7 +3722,7 @@
                 if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()
                         && mIdentifierDisclosureNotifier != null
                         && disclosure != null) {
-                    mIdentifierDisclosureNotifier.addDisclosure(getSubId(), disclosure);
+                    mIdentifierDisclosureNotifier.addDisclosure(mContext, getSubId(), disclosure);
                 }
                 break;
 
@@ -5002,7 +5007,7 @@
     }
 
     private PhoneAccountHandle subscriptionIdToPhoneAccountHandle(final int subId) {
-        final TelecomManager telecomManager = TelecomManager.from(mContext);
+        final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
         final TelephonyManager telephonyManager = TelephonyManager.from(mContext);
         final Iterator<PhoneAccountHandle> phoneAccounts =
             telecomManager.getCallCapablePhoneAccounts(true).listIterator();
@@ -5360,11 +5365,11 @@
         // enable/disable API, so we only toggle the enable state if the unsol events feature
         // flag is enabled.
         if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()) {
-          if (prefEnabled) {
-            mIdentifierDisclosureNotifier.enable();
-          } else {
-            mIdentifierDisclosureNotifier.disable();
-          }
+            if (prefEnabled) {
+                mIdentifierDisclosureNotifier.enable(mContext);
+            } else {
+                mIdentifierDisclosureNotifier.disable(mContext);
+            }
         } else {
             logi("Not toggling enable state for disclosure notifier. Feature flag "
                     + "enable_identifier_disclosure_transparency_unsol_events is disabled");
@@ -5415,4 +5420,12 @@
     public boolean isNullCipherNotificationSupported() {
         return mIsNullCipherNotificationSupported;
     }
+
+    @Override
+    public void refreshSafetySources(String refreshBroadcastId) {
+        if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()
+                || mFeatureFlags.enableModemCipherTransparencyUnsolEvents()) {
+            mSafetySource.refresh(mContext, refreshBroadcastId);
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/NetworkIndication.java b/src/java/com/android/internal/telephony/NetworkIndication.java
index e847060..5c49234 100644
--- a/src/java/com/android/internal/telephony/NetworkIndication.java
+++ b/src/java/com/android/internal/telephony/NetworkIndication.java
@@ -45,7 +45,7 @@
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
 import android.telephony.CellularIdentifierDisclosure;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
 import android.telephony.LinkCapacityEstimate;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhysicalChannelConfig;
@@ -415,7 +415,7 @@
             android.hardware.radio.network.EmergencyRegResult result) {
         mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
 
-        EmergencyRegResult response = RILUtils.convertHalEmergencyRegResult(result);
+        EmergencyRegistrationResult response = RILUtils.convertHalEmergencyRegResult(result);
 
         if (mRil.isLogOrTrace()) {
             mRil.unsljLogRet(RIL_UNSOL_EMERGENCY_NETWORK_SCAN_RESULT, response);
diff --git a/src/java/com/android/internal/telephony/NetworkResponse.java b/src/java/com/android/internal/telephony/NetworkResponse.java
index eb2cd16..b4a37b3 100644
--- a/src/java/com/android/internal/telephony/NetworkResponse.java
+++ b/src/java/com/android/internal/telephony/NetworkResponse.java
@@ -24,7 +24,7 @@
 import android.os.AsyncResult;
 import android.telephony.BarringInfo;
 import android.telephony.CellInfo;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
 import android.telephony.RadioAccessSpecifier;
 import android.telephony.SignalStrength;
 
@@ -429,7 +429,7 @@
         RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
 
         if (rr != null) {
-            EmergencyRegResult response = RILUtils.convertHalEmergencyRegResult(regState);
+            EmergencyRegistrationResult response = RILUtils.convertHalEmergencyRegResult(regState);
             if (responseInfo.error == RadioError.NONE) {
                 RadioResponse.sendMessageResponse(rr.mResult, response);
             }
diff --git a/src/java/com/android/internal/telephony/NetworkTypeController.java b/src/java/com/android/internal/telephony/NetworkTypeController.java
index ea7a6de..b9ad388 100644
--- a/src/java/com/android/internal/telephony/NetworkTypeController.java
+++ b/src/java/com/android/internal/telephony/NetworkTypeController.java
@@ -37,6 +37,9 @@
 import android.telephony.TelephonyManager;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataCallResponse.LinkStatus;
+import android.telephony.data.EpsQos;
+import android.telephony.data.NrQos;
+import android.telephony.data.QosBearerSession;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
@@ -112,8 +115,10 @@
     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;
+    /** Event for qos sessions changed. */
+    private static final int EVENT_QOS_SESSION_CHANGED = 13;
 
-    private static final String[] sEvents = new String[EVENT_DEVICE_IDLE_MODE_CHANGED + 1];
+    private static final String[] sEvents = new String[EVENT_QOS_SESSION_CHANGED + 1];
     static {
         sEvents[EVENT_UPDATE] = "EVENT_UPDATE";
         sEvents[EVENT_QUIT] = "EVENT_QUIT";
@@ -129,6 +134,7 @@
         sEvents[EVENT_INITIALIZE] = "EVENT_INITIALIZE";
         sEvents[EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED] = "EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED";
         sEvents[EVENT_DEVICE_IDLE_MODE_CHANGED] = "EVENT_DEVICE_IDLE_MODE_CHANGED";
+        sEvents[EVENT_QOS_SESSION_CHANGED] = "EVENT_QOS_SESSION_CHANGED";
     }
 
     @NonNull private final Phone mPhone;
@@ -158,6 +164,30 @@
                 }
             };
 
+    @NonNull private final DataNetworkControllerCallback mDataNetworkControllerCallback =
+            new DataNetworkControllerCallback(getHandler()::post) {
+                @Override
+                public void onQosSessionsChanged(
+                        @NonNull List<QosBearerSession> qosBearerSessions) {
+                    if (!mIsTimerResetEnabledOnVoiceQos) return;
+                    sendMessage(obtainMessage(EVENT_QOS_SESSION_CHANGED, qosBearerSessions));
+                }
+
+                @Override
+                public void onNrAdvancedCapableByPcoChanged(boolean nrAdvancedCapable) {
+                    if (mNrAdvancedCapablePcoId <= 0) return;
+                    log("mIsNrAdvancedAllowedByPco=" + nrAdvancedCapable);
+                    mIsNrAdvancedAllowedByPco = nrAdvancedCapable;
+                    sendMessage(EVENT_UPDATE);
+                }
+
+                @Override
+                public void onPhysicalLinkStatusChanged(@LinkStatus int status) {
+                    if (isUsingPhysicalChannelConfigForRrcDetection()) return;
+                    sendMessage(obtainMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED, status));
+                }
+            };
+
     @NonNull private Map<String, OverrideTimerRule> mOverrideTimerRules = new HashMap<>();
     @NonNull private String mLteEnhancedPattern = "";
     @Annotation.OverrideNetworkType private int mOverrideNetworkType;
@@ -165,6 +195,10 @@
     private boolean mIsPrimaryTimerActive;
     private boolean mIsSecondaryTimerActive;
     private boolean mIsTimerResetEnabledForLegacyStateRrcIdle;
+    /** Carrier config to reset timers when mccmnc changes */
+    private boolean mIsTimerResetEnabledOnPlmnChanges;
+    /** Carrier config to reset timers when QCI(LTE) or 5QI(NR) is 1(conversational voice) */
+    private boolean mIsTimerResetEnabledOnVoiceQos;
     private int mLtePlusThresholdBandwidth;
     private int mNrAdvancedThresholdBandwidth;
     private boolean mIncludeLteForNrAdvancedThresholdBandwidth;
@@ -172,6 +206,8 @@
     @NonNull private final Set<Integer> mAdditionalNrAdvancedBands = new HashSet<>();
     @NonNull private String mPrimaryTimerState;
     @NonNull private String mSecondaryTimerState;
+    // TODO(b/316425811 remove the workaround)
+    private int mNrAdvancedBandsSecondaryTimer;
     @NonNull private String mPreviousState;
     @LinkStatus private int mPhysicalLinkStatus;
     private boolean mIsPhysicalChannelConfig16Supported;
@@ -182,15 +218,14 @@
     private boolean mIsDeviceIdleMode = false;
     private boolean mPrimaryCellChangedWhileIdle = false;
 
-    @Nullable private DataNetworkControllerCallback mNrAdvancedCapableByPcoChangedCallback = null;
-    @Nullable private DataNetworkControllerCallback mNrPhysicalLinkStatusChangedCallback = null;
-
     // Cached copies below to prevent race conditions
     @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<>();
+    // TODO(b/316425811 remove the workaround)
+    private boolean mLastShownNrDueToAdvancedBand = false;
     private int mRatchetedNrBandwidths = 0;
     private int mLastAnchorNrCellId = PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN;
     private boolean mDoesPccListIndicateIdle = false;
@@ -271,6 +306,8 @@
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
         mPhone.getDeviceStateMonitor().registerForPhysicalChannelConfigNotifChanged(getHandler(),
                 EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED, null);
+        mPhone.getDataNetworkController().registerDataNetworkControllerCallback(
+                mDataNetworkControllerCallback);
         IntentFilter filter = new IntentFilter();
         filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
         mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
@@ -283,6 +320,8 @@
         mPhone.unregisterForPreferredNetworkTypeChanged(getHandler());
         mPhone.getServiceStateTracker().unregisterForServiceStateChanged(getHandler());
         mPhone.getDeviceStateMonitor().unregisterForPhysicalChannelConfigNotifChanged(getHandler());
+        mPhone.getDataNetworkController().unregisterDataNetworkControllerCallback(
+                mDataNetworkControllerCallback);
         mPhone.getContext().unregisterReceiver(mIntentReceiver);
         CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class);
         if (mCarrierConfigChangeListener != null) {
@@ -304,6 +343,10 @@
                 CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING);
         mIsTimerResetEnabledForLegacyStateRrcIdle = config.getBoolean(
                 CarrierConfigManager.KEY_NR_TIMERS_RESET_IF_NON_ENDC_AND_RRC_IDLE_BOOL);
+        mIsTimerResetEnabledOnPlmnChanges = config.getBoolean(
+                CarrierConfigManager.KEY_NR_TIMERS_RESET_ON_PLMN_CHANGE_BOOL);
+        mIsTimerResetEnabledOnVoiceQos = config.getBoolean(
+                CarrierConfigManager.KEY_NR_TIMERS_RESET_ON_VOICE_QOS_BOOL);
         mLtePlusThresholdBandwidth = config.getInt(
                 CarrierConfigManager.KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT);
         mNrAdvancedThresholdBandwidth = config.getInt(
@@ -322,43 +365,10 @@
         }
         mNrAdvancedCapablePcoId = config.getInt(
                 CarrierConfigManager.KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT);
-        if (mNrAdvancedCapablePcoId > 0 && mNrAdvancedCapableByPcoChangedCallback == null) {
-            mNrAdvancedCapableByPcoChangedCallback =
-                    new DataNetworkControllerCallback(getHandler()::post) {
-                        @Override
-                        public void onNrAdvancedCapableByPcoChanged(boolean nrAdvancedCapable) {
-                            log("mIsNrAdvancedAllowedByPco=" + nrAdvancedCapable);
-                            mIsNrAdvancedAllowedByPco = nrAdvancedCapable;
-                            sendMessage(EVENT_UPDATE);
-                        }
-                    };
-            mPhone.getDataNetworkController().registerDataNetworkControllerCallback(
-                    mNrAdvancedCapableByPcoChangedCallback);
-        } else if (mNrAdvancedCapablePcoId == 0 && mNrAdvancedCapableByPcoChangedCallback != null) {
-            mPhone.getDataNetworkController().unregisterDataNetworkControllerCallback(
-                    mNrAdvancedCapableByPcoChangedCallback);
-            mNrAdvancedCapableByPcoChangedCallback = null;
-        }
         mIsUsingUserDataForRrcDetection = config.getBoolean(
                 CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL);
-        if (!isUsingPhysicalChannelConfigForRrcDetection()) {
-            if (mNrPhysicalLinkStatusChangedCallback == null) {
-                mNrPhysicalLinkStatusChangedCallback =
-                        new DataNetworkControllerCallback(getHandler()::post) {
-                            @Override
-                            public void onPhysicalLinkStatusChanged(@LinkStatus int status) {
-                                sendMessage(obtainMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED,
-                                        new AsyncResult(null, status, null)));
-                            }
-                        };
-                mPhone.getDataNetworkController().registerDataNetworkControllerCallback(
-                        mNrPhysicalLinkStatusChangedCallback);
-            }
-        } else if (mNrPhysicalLinkStatusChangedCallback != null) {
-            mPhone.getDataNetworkController().unregisterDataNetworkControllerCallback(
-                    mNrPhysicalLinkStatusChangedCallback);
-            mNrPhysicalLinkStatusChangedCallback = null;
-        }
+        mNrAdvancedBandsSecondaryTimer = config.getInt(
+                CarrierConfigManager.KEY_NR_ADVANCED_BANDS_SECONDARY_TIMER_SECONDS_INT);
         String nrIconConfiguration = config.getString(
                 CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING);
         String overrideTimerRule = config.getString(
@@ -366,7 +376,8 @@
         String overrideSecondaryTimerRule = config.getString(
                 CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING);
         createTimerRules(nrIconConfiguration, overrideTimerRule, overrideSecondaryTimerRule);
-        updatePhysicalChannelConfigs();
+        updatePhysicalChannelConfigs(
+                mPhone.getServiceStateTracker().getPhysicalChannelConfigList());
     }
 
     private void createTimerRules(String icons, String timers, String secondaryTimers) {
@@ -598,6 +609,7 @@
     private final class DefaultState extends State {
         @Override
         public boolean processMessage(Message msg) {
+            AsyncResult ar;
             if (DBG) log("DefaultState: process " + getEventName(msg.what));
             switch (msg.what) {
                 case EVENT_UPDATE:
@@ -618,17 +630,15 @@
                     parseCarrierConfigs();
                     break;
                 case EVENT_SERVICE_STATE_CHANGED:
-                    mServiceState = mPhone.getServiceStateTracker().getServiceState();
-                    if (DBG) log("ServiceState updated: " + mServiceState);
+                    onServiceStateChanged();
                     transitionToCurrentState();
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    mPhysicalLinkStatus = (int) ar.result;
+                    mPhysicalLinkStatus = msg.arg1;
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED:
-                    AsyncResult result = (AsyncResult) msg.obj;
-                    mIsPhysicalChannelConfigOn = (boolean) result.result;
+                    ar = (AsyncResult) msg.obj;
+                    mIsPhysicalChannelConfigOn = (boolean) ar.result;
                     if (DBG) {
                         log("mIsPhysicalChannelConfigOn changed to: " + mIsPhysicalChannelConfigOn);
                     }
@@ -655,11 +665,17 @@
                     mIsSecondaryTimerActive = false;
                     mSecondaryTimerState = "";
                     updateTimers();
+                    mLastShownNrDueToAdvancedBand = false;
                     updateOverrideNetworkType();
                     break;
                 case EVENT_RADIO_OFF_OR_UNAVAILABLE:
                     if (DBG) log("Reset timers since radio is off or unavailable.");
                     resetAllTimers();
+                    mRatchetedNrBands.clear();
+                    mRatchetedNrBandwidths = 0;
+                    mLastAnchorNrCellId = PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN;
+                    mDoesPccListIndicateIdle = false;
+                    mPhysicalChannelConfigs = null;
                     transitionTo(mLegacyState);
                     break;
                 case EVENT_PREFERRED_NETWORK_MODE_CHANGED:
@@ -668,7 +684,8 @@
                     transitionToCurrentState();
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
-                    updatePhysicalChannelConfigs();
+                    ar = (AsyncResult) msg.obj;
+                    updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                     }
@@ -686,6 +703,24 @@
                     }
                     transitionToCurrentState();
                     break;
+                case EVENT_QOS_SESSION_CHANGED:
+                    List<QosBearerSession> qosBearerSessions = (List<QosBearerSession>) msg.obj;
+                    boolean inVoiceCall = false;
+                    for (QosBearerSession session : qosBearerSessions) {
+                        // TS 23.203 23.501 - 1 means conversational voice
+                        if (session.getQos() instanceof EpsQos qos) {
+                            inVoiceCall = qos.getQci() == 1;
+                        } else if (session.getQos() instanceof NrQos qos) {
+                            inVoiceCall = qos.get5Qi() == 1;
+                        }
+                        if (inVoiceCall) {
+                            if (DBG) log("Device in voice call, reset all timers");
+                            resetAllTimers();
+                            transitionToCurrentState();
+                            break;
+                        }
+                    }
+                    break;
                 default:
                     throw new RuntimeException("Received invalid event: " + msg.what);
             }
@@ -720,10 +755,10 @@
         public boolean processMessage(Message msg) {
             if (DBG) log("LegacyState: process " + getEventName(msg.what));
             updateTimers();
+            AsyncResult ar;
             switch (msg.what) {
                 case EVENT_SERVICE_STATE_CHANGED:
-                    mServiceState = mPhone.getServiceStateTracker().getServiceState();
-                    if (DBG) log("ServiceState updated: " + mServiceState);
+                    onServiceStateChanged();
                     // fallthrough
                 case EVENT_UPDATE:
                     int rat = getDataNetworkType();
@@ -748,18 +783,19 @@
                     mIsNrRestricted = isNrRestricted();
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
-                    updatePhysicalChannelConfigs();
+                    ar = (AsyncResult) msg.obj;
+                    updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                         if (mIsTimerResetEnabledForLegacyStateRrcIdle && !isPhysicalLinkActive()) {
                             if (DBG) log("Reset timers since timer reset is enabled for RRC idle.");
                             resetAllTimers();
+                            updateOverrideNetworkType();
                         }
                     }
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    mPhysicalLinkStatus = (int) ar.result;
+                    mPhysicalLinkStatus = msg.arg1;
                     if (mIsTimerResetEnabledForLegacyStateRrcIdle && !isPhysicalLinkActive()) {
                         if (DBG) log("Reset timers since timer reset is enabled for RRC idle.");
                         resetAllTimers();
@@ -801,10 +837,10 @@
         public boolean processMessage(Message msg) {
             if (DBG) log("IdleState: process " + getEventName(msg.what));
             updateTimers();
+            AsyncResult ar;
             switch (msg.what) {
                 case EVENT_SERVICE_STATE_CHANGED:
-                    mServiceState = mPhone.getServiceStateTracker().getServiceState();
-                    if (DBG) log("ServiceState updated: " + mServiceState);
+                    onServiceStateChanged();
                     // fallthrough
                 case EVENT_UPDATE:
                     int rat = getDataNetworkType();
@@ -829,7 +865,8 @@
                     }
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
-                    updatePhysicalChannelConfigs();
+                    ar = (AsyncResult) msg.obj;
+                    updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                         if (isPhysicalLinkActive()) {
@@ -841,8 +878,7 @@
                     }
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    mPhysicalLinkStatus = (int) ar.result;
+                    mPhysicalLinkStatus = msg.arg1;
                     if (isPhysicalLinkActive()) {
                         transitionWithTimerTo(mLteConnectedState);
                     } else {
@@ -885,10 +921,10 @@
         public boolean processMessage(Message msg) {
             if (DBG) log("LteConnectedState: process " + getEventName(msg.what));
             updateTimers();
+            AsyncResult ar;
             switch (msg.what) {
                 case EVENT_SERVICE_STATE_CHANGED:
-                    mServiceState = mPhone.getServiceStateTracker().getServiceState();
-                    if (DBG) log("ServiceState updated: " + mServiceState);
+                    onServiceStateChanged();
                     // fallthrough
                 case EVENT_UPDATE:
                     int rat = getDataNetworkType();
@@ -913,7 +949,8 @@
                     }
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
-                    updatePhysicalChannelConfigs();
+                    ar = (AsyncResult) msg.obj;
+                    updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                         if (!isPhysicalLinkActive()) {
@@ -925,8 +962,7 @@
                     }
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    mPhysicalLinkStatus = (int) ar.result;
+                    mPhysicalLinkStatus = msg.arg1;
                     if (!isPhysicalLinkActive()) {
                         transitionWithTimerTo(mIdleState);
                     } else {
@@ -969,10 +1005,10 @@
         public boolean processMessage(Message msg) {
             if (DBG) log("NrIdleState: process " + getEventName(msg.what));
             updateTimers();
+            AsyncResult ar;
             switch (msg.what) {
                 case EVENT_SERVICE_STATE_CHANGED:
-                    mServiceState = mPhone.getServiceStateTracker().getServiceState();
-                    if (DBG) log("ServiceState updated: " + mServiceState);
+                    onServiceStateChanged();
                     // fallthrough
                 case EVENT_UPDATE:
                     int rat = getDataNetworkType();
@@ -994,7 +1030,8 @@
                     }
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
-                    updatePhysicalChannelConfigs();
+                    ar = (AsyncResult) msg.obj;
+                    updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                     }
@@ -1006,8 +1043,7 @@
                     }
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    mPhysicalLinkStatus = (int) ar.result;
+                    mPhysicalLinkStatus = msg.arg1;
                     if (isPhysicalLinkActive()) {
                         transitionWithTimerTo(mNrConnectedState);
                     }
@@ -1047,10 +1083,10 @@
         public boolean processMessage(Message msg) {
             if (DBG) log("NrConnectedState: process " + getEventName(msg.what));
             updateTimers();
+            AsyncResult ar;
             switch (msg.what) {
                 case EVENT_SERVICE_STATE_CHANGED:
-                    mServiceState = mPhone.getServiceStateTracker().getServiceState();
-                    if (DBG) log("ServiceState updated: " + mServiceState);
+                    onServiceStateChanged();
                     // fallthrough
                 case EVENT_UPDATE:
                     int rat = getDataNetworkType();
@@ -1072,7 +1108,8 @@
                     }
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
-                    updatePhysicalChannelConfigs();
+                    ar = (AsyncResult) msg.obj;
+                    updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                     }
@@ -1084,8 +1121,7 @@
                     }
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    mPhysicalLinkStatus = (int) ar.result;
+                    mPhysicalLinkStatus = msg.arg1;
                     if (!isPhysicalLinkActive() && mFeatureFlags.supportNrSaRrcIdle()) {
                         transitionWithTimerTo(mNrIdleState);
                     }
@@ -1124,12 +1160,16 @@
 
         @Override
         public boolean processMessage(Message msg) {
-            if (DBG) log("NrConnectedAdvancedState: process " + getEventName(msg.what));
+            mLastShownNrDueToAdvancedBand = isAdditionalNrAdvancedBand(mRatchetedNrBands);
+            if (DBG) {
+                log("NrConnectedAdvancedState: process " + getEventName(msg.what)
+                        + ", been using advanced band is " + mLastShownNrDueToAdvancedBand);
+            }
             updateTimers();
+            AsyncResult ar;
             switch (msg.what) {
                 case EVENT_SERVICE_STATE_CHANGED:
-                    mServiceState = mPhone.getServiceStateTracker().getServiceState();
-                    if (DBG) log("ServiceState updated: " + mServiceState);
+                    onServiceStateChanged();
                     // fallthrough
                 case EVENT_UPDATE:
                     int rat = getDataNetworkType();
@@ -1158,7 +1198,8 @@
                     }
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
-                    updatePhysicalChannelConfigs();
+                    ar = (AsyncResult) msg.obj;
+                    updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                     }
@@ -1170,8 +1211,7 @@
                     }
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    mPhysicalLinkStatus = (int) ar.result;
+                    mPhysicalLinkStatus = msg.arg1;
                     break;
                 default:
                     return NOT_HANDLED;
@@ -1191,9 +1231,19 @@
     private final NrConnectedAdvancedState mNrConnectedAdvancedState =
             new NrConnectedAdvancedState();
 
-    private void updatePhysicalChannelConfigs() {
-        List<PhysicalChannelConfig> physicalChannelConfigs =
-                mPhone.getServiceStateTracker().getPhysicalChannelConfigList();
+    /** On service state changed. */
+    private void onServiceStateChanged() {
+        ServiceState ss = mPhone.getServiceStateTracker().getServiceState();
+        if (mIsTimerResetEnabledOnPlmnChanges
+                && !TextUtils.equals(mServiceState.getOperatorNumeric(), ss.getOperatorNumeric())) {
+            log("Reset any timers due to nr_timers_reset_on_plmn_change_bool");
+            resetAllTimers();
+        }
+        mServiceState = ss;
+        if (DBG) log("ServiceState updated: " + mServiceState);
+    }
+
+    private void updatePhysicalChannelConfigs(List<PhysicalChannelConfig> physicalChannelConfigs) {
         boolean isPccListEmpty = physicalChannelConfigs == null || physicalChannelConfigs.isEmpty();
         if (isPccListEmpty && isUsingPhysicalChannelConfigForRrcDetection()) {
             log("Physical channel configs updated: not updating PCC fields for empty PCC list "
@@ -1249,7 +1299,7 @@
         } else {
             if (mFeatureFlags.supportNrSaRrcIdle() && mDoesPccListIndicateIdle
                     && isUsingPhysicalChannelConfigForRrcDetection()
-                    && !mPrimaryCellChangedWhileIdle && isTimerActiveForNrSaRrcIdle()
+                    && !mPrimaryCellChangedWhileIdle && isTimerActiveForRrcIdle()
                     && !isNrAdvancedForPccFields(nrBandwidths, nrBands)) {
                 log("Allow primary cell change during RRC idle timer without changing state: "
                         + mLastAnchorNrCellId + " -> " + anchorNrCellId);
@@ -1299,6 +1349,10 @@
         }
         if (!mIsDeviceIdleMode && rule != null && rule.getSecondaryTimer(currentName) > 0) {
             int duration = rule.getSecondaryTimer(currentName);
+            if (mLastShownNrDueToAdvancedBand && mNrAdvancedBandsSecondaryTimer > 0) {
+                duration = mNrAdvancedBandsSecondaryTimer;
+                if (DBG) log("timer adjusted by nr_advanced_bands_secondary_timer_seconds_int");
+            }
             if (DBG) log(duration + "s secondary timer started for state: " + currentName);
             mSecondaryTimerState = currentName;
             mPreviousState = currentName;
@@ -1349,7 +1403,9 @@
 
         String currentState = getCurrentState().getName();
 
-        if (mIsPrimaryTimerActive && getOverrideNetworkType() == getCurrentOverrideNetworkType()) {
+        if (mIsPrimaryTimerActive && getOverrideNetworkType() == getCurrentOverrideNetworkType()
+                && getDataNetworkType()
+                == mDisplayInfoController.getTelephonyDisplayInfo().getNetworkType()) {
             // remove primary timer if device goes back to the original icon
             if (DBG) {
                 log("Remove primary timer since icon of primary state and current icon equal: "
@@ -1401,13 +1457,17 @@
         mIsSecondaryTimerActive = false;
         mPrimaryTimerState = "";
         mSecondaryTimerState = "";
+
+        mLastShownNrDueToAdvancedBand = false;
     }
 
-    private boolean isTimerActiveForNrSaRrcIdle() {
+    private boolean isTimerActiveForRrcIdle() {
         if (mIsPrimaryTimerActive) {
-            return mPrimaryTimerState.equals(STATE_CONNECTED_RRC_IDLE);
+            return mPrimaryTimerState.equals(STATE_CONNECTED_RRC_IDLE)
+                    || mPrimaryTimerState.equals(STATE_NOT_RESTRICTED_RRC_IDLE);
         } else if (mIsSecondaryTimerActive) {
-            return mSecondaryTimerState.equals(STATE_CONNECTED_RRC_IDLE);
+            return mSecondaryTimerState.equals(STATE_CONNECTED_RRC_IDLE)
+                    || mSecondaryTimerState.equals(STATE_NOT_RESTRICTED_RRC_IDLE);
         } else {
             return false;
         }
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 3b47670..97eb447 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -5214,6 +5214,12 @@
     }
 
     /**
+     * Refresh the safety sources in response to the identified broadcast.
+     */
+    public void refreshSafetySources(String refreshBroadcastId) {
+    }
+
+    /**
      * Notifies the IMS call status to the modem.
      *
      * @param imsCallInfo The list of {@link ImsCallInfo}.
diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
index 1e0aa3a..7141f37 100644
--- a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
+++ b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
@@ -34,6 +34,7 @@
 import android.telephony.PhoneCapability;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyRegistryManager;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -46,7 +47,10 @@
 import java.util.HashSet;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Optional;
 import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 /**
  * This class manages phone's configuration which defines the potential capability (static) of the
@@ -74,7 +78,10 @@
     private static PhoneConfigurationManager sInstance = null;
     private final Context mContext;
     private PhoneCapability mStaticCapability;
-    private Set<Integer> mSlotsSupportingSimultaneousCellularCalls = new HashSet<>();
+    private final Set<Integer> mSlotsSupportingSimultaneousCellularCalls = new HashSet<>(3);
+    private final Set<Integer> mSubIdsSupportingSimultaneousCellularCalls = new HashSet<>(3);
+    private final HashSet<Consumer<Set<Integer>>> mSimultaneousCellularCallingListeners =
+            new HashSet<>(1);
     private final RadioConfig mRadioConfig;
     private final Handler mHandler;
     // mPhones is obtained from PhoneFactory and can have phones corresponding to inactive modems as
@@ -148,6 +155,25 @@
         }
     }
 
+    /**
+     * Updates the mapping between the slot IDs that support simultaneous calling and the
+     * associated sub IDs as well as notifies listeners.
+     */
+    private void updateSimultaneousSubIdsFromPhoneIdMappingAndNotify() {
+        if (!mFeatureFlags.simultaneousCallingIndications()) return;
+        Set<Integer> slotCandidates = mSlotsSupportingSimultaneousCellularCalls.stream()
+                .map(i -> mPhones[i].getSubId())
+                .filter(i ->i > SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+                        .collect(Collectors.toSet());
+        if (mSubIdsSupportingSimultaneousCellularCalls.equals(slotCandidates))  return;
+        log("updateSimultaneousSubIdsFromPhoneIdMapping update: "
+                + mSubIdsSupportingSimultaneousCellularCalls + " -> " + slotCandidates);
+        mSubIdsSupportingSimultaneousCellularCalls.clear();
+        mSubIdsSupportingSimultaneousCellularCalls.addAll(slotCandidates);
+        mNotifier.notifySimultaneousCellularCallingSubscriptionsChanged(
+                mSubIdsSupportingSimultaneousCellularCalls);
+    }
+
     private void registerForRadioState(Phone phone) {
         phone.mCi.registerForAvailable(mHandler, Phone.EVENT_RADIO_AVAILABLE, phone);
     }
@@ -163,12 +189,16 @@
     /**
      * If virtual DSDA is enabled for this UE, then increase maxActiveVoiceSubscriptions to 2.
      */
-    private PhoneCapability maybeUpdateMaxActiveVoiceSubscriptions(
+    private PhoneCapability maybeOverrideMaxActiveVoiceSubscriptions(
             final PhoneCapability staticCapability) {
-        if (staticCapability.getLogicalModemList().size() > 1 && mVirtualDsdaEnabled) {
+        boolean isVDsdaEnabled = staticCapability.getLogicalModemList().size() > 1
+                && mVirtualDsdaEnabled;
+        boolean isBkwdCompatDsdaEnabled = mFeatureFlags.simultaneousCallingIndications()
+                && mMi.getMultiSimProperty().orElse(SSSS).equals(DSDA);
+        if (isVDsdaEnabled || isBkwdCompatDsdaEnabled) {
             // Since we already initialized maxActiveVoiceSubscriptions to the count the
-            // modem is capable of, vDSDA is only able to increase that count via this method. We do
-            // not allow vDSDA to decrease maxActiveVoiceSubscriptions:
+            // modem is capable of, we are only able to increase that count via this method. We do
+            // not allow a decrease of maxActiveVoiceSubscriptions:
             int updatedMaxActiveVoiceSubscriptions =
                     Math.max(staticCapability.getMaxActiveVoiceSubscriptions(), 2);
             return new PhoneCapability.Builder(staticCapability)
@@ -180,13 +210,39 @@
     }
 
     private void maybeEnableCellularDSDASupport() {
-        if (mRadioConfig != null && mRadioConfig.getRadioConfigProxy(null)
-                .getVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_2_2) &&
-                getPhoneCount() > 1 &&
-                mStaticCapability.getMaxActiveVoiceSubscriptions() > 1) {
+        boolean bkwdsCompatDsda = mFeatureFlags.simultaneousCallingIndications()
+                && getPhoneCount() > 1
+                && mMi.getMultiSimProperty().orElse(SSSS).equals(DSDA);
+        boolean halSupportSimulCalling = mRadioConfig != null
+                && mRadioConfig.getRadioConfigProxy(null).getVersion().greaterOrEqual(
+                        RIL.RADIO_HAL_VERSION_2_2)
+                && getPhoneCount() > 1 && mStaticCapability.getMaxActiveVoiceSubscriptions() > 1;
+        // Register for simultaneous calling support changes in the modem if the HAL supports it
+        if (halSupportSimulCalling) {
             updateSimultaneousCallingSupport();
             mRadioConfig.registerForSimultaneousCallingSupportStatusChanged(mHandler,
                     EVENT_SIMULTANEOUS_CALLING_SUPPORT_CHANGED, null);
+        } else if (bkwdsCompatDsda) {
+            // For older devices that only declare that they support DSDA via modem config,
+            // set DSDA as capable now statically.
+            log("DSDA modem config detected - setting DSDA enabled");
+            for (Phone p : mPhones) {
+                mSlotsSupportingSimultaneousCellularCalls.add(p.getPhoneId());
+            }
+            updateSimultaneousSubIdsFromPhoneIdMappingAndNotify();
+            notifySimultaneousCellularCallingSlotsChanged();
+        }
+        // Register for subId updates to notify listeners when simultaneous calling is configured
+        if (mFeatureFlags.simultaneousCallingIndications()
+                && (bkwdsCompatDsda || halSupportSimulCalling)) {
+            mContext.getSystemService(TelephonyRegistryManager.class)
+                    .addOnSubscriptionsChangedListener(
+                            new SubscriptionManager.OnSubscriptionsChangedListener() {
+                                @Override
+                                public void onSubscriptionsChanged() {
+                                    updateSimultaneousSubIdsFromPhoneIdMappingAndNotify();
+                                }
+                            }, mHandler::post);
         }
     }
 
@@ -291,7 +347,6 @@
                         if (mSlotsSupportingSimultaneousCellularCalls.size() > getPhoneCount()) {
                             loge("Invalid size of DSDA slots. Disabling cellular DSDA.");
                             mSlotsSupportingSimultaneousCellularCalls.clear();
-                            break;
                         }
                     } else {
                         log(msg.what + " failure. Not getting logical slots that support "
@@ -299,8 +354,8 @@
                         mSlotsSupportingSimultaneousCellularCalls.clear();
                     }
                     if (mFeatureFlags.simultaneousCallingIndications()) {
-                        mNotifier.notifySimultaneousCellularCallingSubscriptionsChanged(
-                                mSlotsSupportingSimultaneousCellularCalls);
+                        updateSimultaneousSubIdsFromPhoneIdMappingAndNotify();
+                        notifySimultaneousCellularCallingSlotsChanged();
                     }
                     break;
                 default:
@@ -439,7 +494,7 @@
                     mHandler, EVENT_GET_PHONE_CAPABILITY_DONE);
             mRadioConfig.getPhoneCapability(callback);
         }
-        mStaticCapability = maybeUpdateMaxActiveVoiceSubscriptions(mStaticCapability);
+        mStaticCapability = maybeOverrideMaxActiveVoiceSubscriptions(mStaticCapability);
         log("getStaticPhoneCapability: mStaticCapability " + mStaticCapability);
         return mStaticCapability;
     }
@@ -455,8 +510,31 @@
         return mStaticCapability.getMaxActiveDataSubscriptions();
     }
 
+    /**
+     * Register to listen to changes in the Phone slots that support simultaneous calling.
+     * @param consumer A consumer that will be used to consume the new slots supporting simultaneous
+     *                 cellular calling when it changes.
+     */
+    public void registerForSimultaneousCellularCallingSlotsChanged(
+            Consumer<Set<Integer>> consumer) {
+        mSimultaneousCellularCallingListeners.add(consumer);
+    }
+
+    private void notifySimultaneousCellularCallingSlotsChanged() {
+        log("notifying listeners of changes to simultaneous cellular calling - new state:"
+                + mSlotsSupportingSimultaneousCellularCalls);
+        for (Consumer<Set<Integer>> consumer : mSimultaneousCellularCallingListeners) {
+            try {
+                consumer.accept(new HashSet<>(mSlotsSupportingSimultaneousCellularCalls));
+            } catch (Exception e) {
+                log("Unexpected Exception encountered when notifying listener: " + e);
+            }
+        }
+    }
+
     private void notifyCapabilityChanged() {
-        mNotifier.notifyPhoneCapabilityChanged(mStaticCapability);
+        mNotifier.notifyPhoneCapabilityChanged(maybeOverrideMaxActiveVoiceSubscriptions(
+                mStaticCapability));
     }
 
     /**
@@ -540,6 +618,10 @@
             } else {
                 // The number of active modems is 0 or 1, disable cellular DSDA:
                 mSlotsSupportingSimultaneousCellularCalls.clear();
+                if (mFeatureFlags.simultaneousCallingIndications()) {
+                    updateSimultaneousSubIdsFromPhoneIdMappingAndNotify();
+                    notifySimultaneousCellularCallingSlotsChanged();
+                }
             }
 
             // When the user enables DSDS mode, the default VOICE and SMS subId should be switched
@@ -717,6 +799,13 @@
                 Context context, int numOfActiveModems) {
             PhoneFactory.onMultiSimConfigChanged(context, numOfActiveModems);
         }
+
+        /**
+         * Wrapper function to query the sysprop for multi_sim_config
+         */
+        public Optional<String> getMultiSimProperty() {
+            return TelephonyProperties.multi_sim_config();
+        }
     }
 
     private static void log(String s) {
diff --git a/src/java/com/android/internal/telephony/RILUtils.java b/src/java/com/android/internal/telephony/RILUtils.java
index 0617656..8897db4 100644
--- a/src/java/com/android/internal/telephony/RILUtils.java
+++ b/src/java/com/android/internal/telephony/RILUtils.java
@@ -305,10 +305,10 @@
 import android.net.LinkProperties;
 import android.os.SystemClock;
 import android.service.carrier.CarrierIdentifier;
-import android.telephony.CarrierInfo;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation;
 import android.telephony.BarringInfo;
+import android.telephony.CarrierInfo;
 import android.telephony.CarrierRestrictionRules;
 import android.telephony.CellConfigLte;
 import android.telephony.CellIdentity;
@@ -335,7 +335,7 @@
 import android.telephony.CellularIdentifierDisclosure;
 import android.telephony.ClosedSubscriberGroupInfo;
 import android.telephony.DomainSelectionService;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
 import android.telephony.LinkCapacityEstimate;
 import android.telephony.ModemInfo;
 import android.telephony.NetworkRegistrationInfo;
@@ -4546,13 +4546,13 @@
     }
 
     /**
-     * Convert EmergencyRegResult.aidl to EmergencyRegResult.
+     * Convert EmergencyRegResult.aidl to EmergencyRegistrationResult.
      * @param halResult EmergencyRegResult.aidl in HAL.
-     * @return Converted EmergencyRegResult.
+     * @return Converted EmergencyRegistrationResult.
      */
-    public static EmergencyRegResult convertHalEmergencyRegResult(
+    public static EmergencyRegistrationResult convertHalEmergencyRegResult(
             android.hardware.radio.network.EmergencyRegResult halResult) {
-        return new EmergencyRegResult(
+        return new EmergencyRegistrationResult(
                 halResult.accessNetwork,
                 convertHalRegState(halResult.regState),
                 halResult.emcDomain,
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index 54c27c5..04f5c08 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -2544,7 +2544,13 @@
         /** Return if the SMS was originated from the default SMS application. */
         public boolean isFromDefaultSmsApplication(Context context) {
             if (mIsFromDefaultSmsApplication == null) {
-                UserHandle userHandle = TelephonyUtils.getSubscriptionUserHandle(context, mSubId);
+                UserHandle userHandle;
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    userHandle = TelephonyUtils.getSubscriptionUserHandle(context, mSubId);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
                 // Perform a lazy initialization, due to the cost of the operation.
                 mIsFromDefaultSmsApplication = SmsApplication.isDefaultSmsApplicationAsUser(context,
                                     getAppPackageName(), userHandle);
diff --git a/src/java/com/android/internal/telephony/SimResponse.java b/src/java/com/android/internal/telephony/SimResponse.java
index 164ec7d..59defc3 100644
--- a/src/java/com/android/internal/telephony/SimResponse.java
+++ b/src/java/com/android/internal/telephony/SimResponse.java
@@ -112,6 +112,8 @@
             android.hardware.radio.sim.CarrierRestrictions carrierRestrictions,
             int multiSimPolicy) {
         RILRequest rr = mRil.processResponse(HAL_SERVICE_SIM, responseInfo);
+        boolean carrierLockInfoSupported = mRil.getHalVersion(HAL_SERVICE_SIM).greater(
+                RIL.RADIO_HAL_VERSION_2_2);
         if (rr == null) {
             return;
         }
@@ -132,7 +134,8 @@
                 RILUtils.convertAidlCarrierInfoList(
                         carrierRestrictions.allowedCarrierInfoList)).setExcludedCarrierInfo(
                 RILUtils.convertAidlCarrierInfoList(
-                        carrierRestrictions.excludedCarrierInfoList)).build();
+                        carrierRestrictions.excludedCarrierInfoList)).setCarrierLockInfoFeature(
+                carrierLockInfoSupported).build();
         if (responseInfo.error == RadioError.NONE) {
             RadioResponse.sendMessageResponse(rr.mResult, ret);
         }
diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
index 8b41f6e..f5aa074 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -48,6 +48,7 @@
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
 import com.android.internal.telephony.nitz.NitzStateMachineImpl;
 import com.android.internal.telephony.security.CellularIdentifierDisclosureNotifier;
+import com.android.internal.telephony.security.CellularNetworkSecuritySafetySource;
 import com.android.internal.telephony.security.NullCipherNotifier;
 import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.UiccCard;
@@ -278,8 +279,14 @@
         return sInstance;
     }
 
-    public GsmCdmaCallTracker makeGsmCdmaCallTracker(GsmCdmaPhone phone) {
-        return new GsmCdmaCallTracker(phone);
+    /**
+     * Create a new GsmCdmaCallTracker
+     * @param phone GsmCdmaPhone
+     * @param featureFlags Telephony feature flag
+     */
+    public GsmCdmaCallTracker makeGsmCdmaCallTracker(GsmCdmaPhone phone,
+            @NonNull FeatureFlags featureFlags) {
+        return new GsmCdmaCallTracker(phone, featureFlags);
     }
 
     public SmsStorageMonitor makeSmsStorageMonitor(Phone phone) {
@@ -569,9 +576,16 @@
         return new DataSettingsManager(phone, dataNetworkController, looper, callback);
     }
 
+    /** Create CellularNetworkSecuritySafetySource. */
+    public CellularNetworkSecuritySafetySource makeCellularNetworkSecuritySafetySource(
+            Context context) {
+        return CellularNetworkSecuritySafetySource.getInstance(context);
+    }
+
     /** Create CellularIdentifierDisclosureNotifier. */
-    public CellularIdentifierDisclosureNotifier makeIdentifierDisclosureNotifier() {
-        return CellularIdentifierDisclosureNotifier.getInstance();
+    public CellularIdentifierDisclosureNotifier makeIdentifierDisclosureNotifier(
+            CellularNetworkSecuritySafetySource safetySource) {
+        return CellularIdentifierDisclosureNotifier.getInstance(safetySource);
     }
 
     /** Create NullCipherNotifier. */
diff --git a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
index 8cffe72..02c459a 100644
--- a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
+++ b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
@@ -23,6 +23,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.AlarmManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -34,6 +35,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.SystemClock;
 import android.provider.Settings;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.NetworkRegistrationInfo;
@@ -41,7 +43,6 @@
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyDisplayInfo;
 import android.util.IndentingPrintWriter;
 import android.util.LocalLog;
@@ -61,7 +62,10 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -107,7 +111,7 @@
     /** 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;
+    private static final int EVENT_STABILITY_CHECK_PASSED = 5;
     /** Event when subscriptions changed. */
     private static final int EVENT_SUBSCRIPTIONS_CHANGED = 6;
 
@@ -126,22 +130,54 @@
     /** Notification ID **/
     private static final int AUTO_DATA_SWITCH_NOTIFICATION_ID = 1;
 
+    /**
+     * The threshold of long timer, longer than or equal to which we use alarm manager to schedule
+     * instead of handler.
+     */
+    private static final long RETRY_LONG_DELAY_TIMER_THRESHOLD_MILLIS = TimeUnit
+            .MINUTES.toMillis(1);
+
     private final @NonNull LocalLog mLocalLog = new LocalLog(128);
     private final @NonNull Context mContext;
     private static @NonNull FeatureFlags sFeatureFlags = new FeatureFlagsImpl();
     private final @NonNull SubscriptionManagerService mSubscriptionManagerService;
     private final @NonNull PhoneSwitcher mPhoneSwitcher;
     private final @NonNull AutoDataSwitchControllerCallback mPhoneSwitcherCallback;
+    private final @NonNull AlarmManager mAlarmManager;
+    /** A map of a scheduled event to its associated extra for action when the event fires off. */
+    private final @NonNull Map<Integer, Object> mScheduledEventsToExtras;
+    /** A map of an event to its associated alarm listener callback for when the event fires off. */
+    private final @NonNull Map<Integer, AlarmManager.OnAlarmListener> mEventsToAlarmListener;
+    /**
+     * Event extras for checking environment stability.
+     * @param targetPhoneId The target phone Id to switch to when the stability check pass.
+     * @param isForPerformance Whether the switch is due to RAT/signal strength performance.
+     * @param needValidation Whether ping test needs to pass.
+     */
+    private record StabilityEventExtra(int targetPhoneId, boolean isForPerformance,
+                               boolean needValidation) {}
+
+    /**
+     * Event extras for evaluating switch environment.
+     * @param evaluateReason The reason that triggers the evaluation.
+     */
+    private record EvaluateEventExtra(@AutoDataSwitchEvaluationReason int evaluateReason) {}
     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.
+     * Configurable time threshold in ms to define an 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;
     /**
+     * Configurable time threshold in ms to define an internet connection performance status to be
+     * stable (e.g. LTE + 4 signal strength, UMTS + 2 signal strength), while -1 indicates
+     * auto switch feature based on RAT/SS is disabled.
+     */
+    private long mAutoDataSwitchPerformanceStabilityTimeThreshold = -1;
+    /**
      * 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 will always switch to the higher score
      * SIM. If < 0, the network type and signal strength based auto switch is disabled.
@@ -250,8 +286,8 @@
             return "{phone " + mPhone.getPhoneId()
                     + " score=" + getRatSignalScore() + " dataRegState="
                     + NetworkRegistrationInfo.registrationStateToString(mDataRegState)
-                    + " " + getUsableState()
-                    + " display=" + mDisplayInfo + " signalStrength=" + mSignalStrength.getLevel()
+                    + " " + getUsableState() + " " + mDisplayInfo
+                    + " signalStrength=" + mSignalStrength.getLevel()
                     + " listeningForEvents=" + mListeningForEvents
                     + "}";
 
@@ -296,9 +332,12 @@
         super(looper);
         mContext = context;
         sFeatureFlags = featureFlags;
+        mPhoneSwitcherCallback = phoneSwitcherCallback;
+        mAlarmManager = context.getSystemService(AlarmManager.class);
+        mScheduledEventsToExtras = new HashMap<>();
+        mEventsToAlarmListener = new HashMap<>();
         mSubscriptionManagerService = SubscriptionManagerService.getInstance();
         mPhoneSwitcher = phoneSwitcher;
-        mPhoneSwitcherCallback = phoneSwitcherCallback;
         readDeviceResourceConfig();
         int numActiveModems = PhoneFactory.getPhones().length;
         mPhonesSignalStatus = new PhoneSignalStatus[numActiveModems];
@@ -407,6 +446,8 @@
         mRequirePingTestBeforeSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired();
         mAutoDataSwitchAvailabilityStabilityTimeThreshold =
                 dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold();
+        mAutoDataSwitchPerformanceStabilityTimeThreshold =
+                dataConfig.getAutoDataSwitchPerformanceStabilityTimeThreshold();
         mAutoDataSwitchValidationMaxRetry =
                 dataConfig.getAutoDataSwitchValidationMaxRetry();
     }
@@ -432,15 +473,35 @@
                 onSignalStrengthChanged(phoneId);
                 break;
             case EVENT_EVALUATE_AUTO_SWITCH:
-                int reason = (int) msg.obj;
-                onEvaluateAutoDataSwitch(reason);
+                if (sFeatureFlags.autoDataSwitchRatSs()) {
+                    Object obj = mScheduledEventsToExtras.get(EVENT_EVALUATE_AUTO_SWITCH);
+                    if (obj instanceof EvaluateEventExtra extra) {
+                        mScheduledEventsToExtras.remove(EVENT_EVALUATE_AUTO_SWITCH);
+                        onEvaluateAutoDataSwitch(extra.evaluateReason);
+                    }
+                } else {
+                    int reason = (int) msg.obj;
+                    onEvaluateAutoDataSwitch(reason);
+                }
                 break;
-            case EVENT_MEETS_AUTO_DATA_SWITCH_STATE:
-                int targetPhoneId = msg.arg1;
-                boolean needValidation = msg.arg2 == 1;
-                log("require validation on phone " + targetPhoneId
-                        + (needValidation ? "" : " no") + " need to pass");
-                mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation);
+            case EVENT_STABILITY_CHECK_PASSED:
+                if (sFeatureFlags.autoDataSwitchRatSs()) {
+                    Object obj = mScheduledEventsToExtras.get(EVENT_STABILITY_CHECK_PASSED);
+                    if (obj instanceof StabilityEventExtra extra) {
+                        int targetPhoneId = extra.targetPhoneId;
+                        boolean needValidation = extra.needValidation;
+                        log("require validation on phone " + targetPhoneId
+                                + (needValidation ? "" : " no") + " need to pass");
+                        mScheduledEventsToExtras.remove(EVENT_STABILITY_CHECK_PASSED);
+                        mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation);
+                    }
+                } else {
+                    int targetPhoneId = msg.arg1;
+                    boolean needValidation = msg.arg2 == 1;
+                    log("require validation on phone " + targetPhoneId
+                            + (needValidation ? "" : " no") + " need to pass");
+                    mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation);
+                }
                 break;
             case EVENT_SUBSCRIPTIONS_CHANGED:
                 onSubscriptionsChanged();
@@ -533,16 +594,21 @@
 
     /**
      * Called as a preliminary check for the frequent signal/display info change.
-     * @return The phone Id if found a candidate phone with higher signal score.
+     * @return The phone Id if found a candidate phone with higher signal score, or the DDS has
+     * an equal score.
      */
     private int getHigherScoreCandidatePhoneId() {
         int preferredPhoneId = mPhoneSwitcher.getPreferredDataPhoneId();
-        if (isActiveModemPhone(preferredPhoneId)) {
+        int ddsPhoneId = mSubscriptionManagerService.getPhoneId(
+                mSubscriptionManagerService.getDefaultDataSubId());
+        if (isActiveModemPhone(preferredPhoneId) && isActiveModemPhone(ddsPhoneId)) {
             int currentScore = mPhonesSignalStatus[preferredPhoneId].getRatSignalScore();
             for (int phoneId = 0; phoneId < mPhonesSignalStatus.length; phoneId++) {
+                if (phoneId == preferredPhoneId) continue;
                 int candidateScore = mPhonesSignalStatus[phoneId].getRatSignalScore();
-                if (phoneId != preferredPhoneId
-                        && (candidateScore - currentScore) > mScoreTolerance) {
+                if ((candidateScore - currentScore) > mScoreTolerance
+                        // Also reevaluate if DDS has the same score as the current phone.
+                        || (candidateScore >= currentScore && phoneId == ddsPhoneId)) {
                     return phoneId;
                 }
             }
@@ -559,8 +625,15 @@
                 ? mAutoDataSwitchAvailabilityStabilityTimeThreshold
                 << mAutoSwitchValidationFailedCount
                 : 0;
-        if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) {
-            sendMessageDelayed(obtainMessage(EVENT_EVALUATE_AUTO_SWITCH, reason), delayMs);
+        if (sFeatureFlags.autoDataSwitchRatSs()) {
+            if (!mScheduledEventsToExtras.containsKey(EVENT_EVALUATE_AUTO_SWITCH)) {
+                scheduleEventWithTimer(EVENT_EVALUATE_AUTO_SWITCH, new EvaluateEventExtra(reason),
+                        delayMs);
+            }
+        } else {
+            if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) {
+                sendMessageDelayed(obtainMessage(EVENT_EVALUATE_AUTO_SWITCH, reason), delayMs);
+            }
         }
     }
 
@@ -576,14 +649,15 @@
         int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId();
         // check is valid DSDS
         if (mSubscriptionManagerService.getActiveSubIdList(true).length < 2) return;
-        Phone defaultDataPhone = PhoneFactory.getPhone(mSubscriptionManagerService.getPhoneId(
-                defaultDataSubId));
+        int defaultDataPhoneId = mSubscriptionManagerService.getPhoneId(
+                defaultDataSubId);
+        Phone defaultDataPhone = PhoneFactory.getPhone(defaultDataPhoneId);
         if (defaultDataPhone == null) {
             loge("onEvaluateAutoDataSwitch: cannot find the phone associated with default data"
                     + " subscription " + defaultDataSubId);
             return;
         }
-        int defaultDataPhoneId = defaultDataPhone.getPhoneId();
+
         int preferredPhoneId = mPhoneSwitcher.getPreferredDataPhoneId();
         StringBuilder debugMessage = new StringBuilder("onEvaluateAutoDataSwitch:");
         debugMessage.append(" defaultPhoneId: ").append(defaultDataPhoneId)
@@ -591,11 +665,11 @@
                 .append(", reason: ").append(evaluationReasonToString(reason));
         if (preferredPhoneId == defaultDataPhoneId) {
             // on default data sub
-            int candidatePhoneId = getSwitchCandidatePhoneId(defaultDataPhoneId, debugMessage);
+            StabilityEventExtra res = evaluateAnyCandidateToUse(defaultDataPhoneId, debugMessage);
             log(debugMessage.toString());
-            if (candidatePhoneId != INVALID_PHONE_INDEX) {
-                mSelectedTargetPhoneId = candidatePhoneId;
-                startStabilityCheck(candidatePhoneId, mRequirePingTestBeforeSwitch);
+            if (res.targetPhoneId != INVALID_PHONE_INDEX) {
+                mSelectedTargetPhoneId = res.targetPhoneId;
+                startStabilityCheck(res.targetPhoneId, res.isForPerformance, res.needValidation);
             } else {
                 cancelAnyPendingSwitch();
             }
@@ -617,6 +691,7 @@
             }
 
             boolean backToDefault = false;
+            boolean isForPerformance = false;
             boolean needValidation = true;
 
             if (sFeatureFlags.autoSwitchAllowRoaming()) {
@@ -654,12 +729,13 @@
                                         .getRatSignalScore();
                                 int currentScore = mPhonesSignalStatus[preferredPhoneId]
                                         .getRatSignalScore();
-                                if ((defaultScore - currentScore) > mScoreTolerance) {
+                                if (defaultScore >= currentScore) {
                                     debugMessage
-                                            .append(", back to default for higher score ")
+                                            .append(", back to default for higher or equal score ")
                                             .append(defaultScore).append(" versus current ")
                                             .append(currentScore);
                                     backToDefault = true;
+                                    isForPerformance = true;
                                     needValidation = mRequirePingTestBeforeSwitch;
                                 }
                             } else {
@@ -688,12 +764,13 @@
                 } else if (isRatSignalStrengthBasedSwitchEnabled()) {
                     int defaultScore = mPhonesSignalStatus[defaultDataPhoneId].getRatSignalScore();
                     int currentScore = mPhonesSignalStatus[preferredPhoneId].getRatSignalScore();
-                    if ((defaultScore - currentScore) > mScoreTolerance) {
+                    if (defaultScore >= currentScore) {
                         debugMessage
-                                .append(", back to default as default phone has higher score ")
+                                .append(", back to default as default has higher or equal score ")
                                 .append(defaultScore).append(" versus current ")
                                 .append(currentScore);
                         backToDefault = true;
+                        isForPerformance = true;
                         needValidation = mRequirePingTestBeforeSwitch;
                     }
                 } else if (isInService(mPhonesSignalStatus[defaultDataPhoneId].mDataRegState)) {
@@ -706,7 +783,7 @@
             if (backToDefault) {
                 log(debugMessage.toString());
                 mSelectedTargetPhoneId = defaultDataPhoneId;
-                startStabilityCheck(DEFAULT_PHONE_INDEX, needValidation);
+                startStabilityCheck(DEFAULT_PHONE_INDEX, isForPerformance, needValidation);
             } else {
                 // cancel any previous attempts of switching back to default phone
                 cancelAnyPendingSwitch();
@@ -718,25 +795,29 @@
      * Called when consider switching from primary default data sub to another data sub.
      * @param defaultPhoneId The default data phone
      * @param debugMessage Debug message.
-     * @return the target subId if a suitable candidate is found, otherwise return
-     * {@link SubscriptionManager#INVALID_PHONE_INDEX}
+     * @return StabilityEventExtra As evaluation result.
      */
-    private int getSwitchCandidatePhoneId(int defaultPhoneId, @NonNull StringBuilder debugMessage) {
+    @NonNull private StabilityEventExtra evaluateAnyCandidateToUse(int defaultPhoneId,
+            @NonNull StringBuilder debugMessage) {
         Phone defaultDataPhone = PhoneFactory.getPhone(defaultPhoneId);
+        boolean isForPerformance = false;
+        StabilityEventExtra invalidResult = new StabilityEventExtra(INVALID_PHONE_INDEX,
+                isForPerformance, mRequirePingTestBeforeSwitch);
+
         if (defaultDataPhone == null) {
             debugMessage.append(", no candidate as no sim loaded");
-            return INVALID_PHONE_INDEX;
+            return invalidResult;
         }
 
         if (!defaultDataPhone.isUserDataEnabled()) {
             debugMessage.append(", no candidate as user disabled mobile data");
-            return INVALID_PHONE_INDEX;
+            return invalidResult;
         }
 
         if (mDefaultNetworkIsOnNonCellular) {
             debugMessage.append(", no candidate as default network is active")
                     .append(" on non-cellular transport");
-            return INVALID_PHONE_INDEX;
+            return invalidResult;
         }
 
         if (sFeatureFlags.autoSwitchAllowRoaming()) {
@@ -744,14 +825,14 @@
             if (!isRatSignalStrengthBasedSwitchEnabled()
                     && isHomeService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) {
                 debugMessage.append(", no candidate as default phone is in HOME service");
-                return INVALID_PHONE_INDEX;
+                return invalidResult;
             }
         } else {
             // check whether primary and secondary signal status are worth switching
             if (!isRatSignalStrengthBasedSwitchEnabled()
                     && isInService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) {
                 debugMessage.append(", no candidate as default phone is in service");
-                return INVALID_PHONE_INDEX;
+                return invalidResult;
             }
         }
 
@@ -764,27 +845,27 @@
             if (sFeatureFlags.autoSwitchAllowRoaming()) {
                 PhoneSignalStatus.UsableState currentUsableState =
                         mPhonesSignalStatus[defaultPhoneId].getUsableState();
-                PhoneSignalStatus.UsableState candidatePhoneUsableRank =
+                PhoneSignalStatus.UsableState candidateUsableState =
                         mPhonesSignalStatus[phoneId].getUsableState();
-                debugMessage.append(", found phone ").append(phoneId).append(" is ").append(
-                        candidatePhoneUsableRank)
-                        .append(", current is ").append(currentUsableState);
-                if (candidatePhoneUsableRank.mScore > currentUsableState.mScore) {
+                debugMessage.append(", found phone ").append(phoneId).append(" ")
+                        .append(candidateUsableState)
+                        .append(", default is ").append(currentUsableState);
+                if (candidateUsableState.mScore > currentUsableState.mScore) {
                     secondaryDataPhone = PhoneFactory.getPhone(phoneId);
                 } else if (isRatSignalStrengthBasedSwitchEnabled()
-                        && currentUsableState.mScore == candidatePhoneUsableRank.mScore) {
+                        && currentUsableState.mScore == candidateUsableState.mScore) {
                     // Both phones are home or both roaming enabled, so compare RAT/signal score.
 
                     int defaultScore = defaultPhoneStatus.getRatSignalScore();
                     int candidateScore = candidatePhoneStatus.getRatSignalScore();
                     if ((candidateScore - defaultScore) > mScoreTolerance) {
-                        debugMessage.append(" with higher score ").append(
-                                        candidateScore)
-                                .append(" versus current ").append(defaultScore);
+                        debugMessage.append(" with ").append(defaultScore)
+                                .append(" versus candidate higher score ").append(candidateScore);
                         secondaryDataPhone = PhoneFactory.getPhone(phoneId);
+                        isForPerformance = true;
                     } else {
-                        debugMessage.append(", but its score ").append(candidateScore)
-                                .append(" doesn't meet the bar to switch given the current ")
+                        debugMessage.append(", candidate's score ").append(candidateScore)
+                                .append(" doesn't justify the switch given the current ")
                                 .append(defaultScore);
                     }
                 }
@@ -802,6 +883,7 @@
                             debugMessage.append(" with higher score ").append(candidateScore)
                                     .append(" versus current ").append(defaultScore);
                             secondaryDataPhone = PhoneFactory.getPhone(phoneId);
+                            isForPerformance = true;
                         } else {
                             debugMessage.append(", but its score ").append(candidateScore)
                                     .append(" doesn't meet the bar to switch given the current ")
@@ -817,21 +899,23 @@
             if (secondaryDataPhone != null) {
                 // check auto switch feature enabled
                 if (secondaryDataPhone.isDataAllowed()) {
-                    return phoneId;
+                    return new StabilityEventExtra(phoneId,
+                            isForPerformance, mRequirePingTestBeforeSwitch);
                 } else {
-                    debugMessage.append(", but its data is not allowed");
+                    debugMessage.append(", but candidate's data is not allowed");
                 }
             }
         }
         debugMessage.append(", found no qualified candidate.");
-        return INVALID_PHONE_INDEX;
+        return invalidResult;
     }
 
     /**
      * @return {@code true} If the feature of switching base on RAT and signal strength is enabled.
      */
     private boolean isRatSignalStrengthBasedSwitchEnabled() {
-        return sFeatureFlags.autoDataSwitchRatSs() && mScoreTolerance >= 0;
+        return sFeatureFlags.autoDataSwitchRatSs() && mScoreTolerance >= 0
+                && mAutoDataSwitchPerformanceStabilityTimeThreshold >= 0;
     }
 
     /**
@@ -839,18 +923,77 @@
      * Start pre-switch validation if the current environment suits auto data switch for
      * {@link #mAutoDataSwitchAvailabilityStabilityTimeThreshold} MS.
      * @param targetPhoneId the target phone Id.
+     * @param isForPerformance {@code true} entails longer stability check.
      * @param needValidation {@code true} if validation is needed.
      */
-    private void startStabilityCheck(int targetPhoneId, boolean needValidation) {
-        log("startAutoDataSwitchStabilityCheck: targetPhoneId=" + targetPhoneId
-                + " needValidation=" + needValidation);
+    private void startStabilityCheck(int targetPhoneId, boolean isForPerformance,
+            boolean needValidation) {
         String combinationIdentifier = targetPhoneId + "" + needValidation;
-        if (!hasEqualMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, combinationIdentifier)) {
-            removeMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE);
-            sendMessageDelayed(obtainMessage(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, targetPhoneId,
+        if (sFeatureFlags.autoDataSwitchRatSs()) {
+            StabilityEventExtra eventExtras = (StabilityEventExtra)
+                    mScheduledEventsToExtras.getOrDefault(EVENT_STABILITY_CHECK_PASSED,
+                            new StabilityEventExtra(INVALID_PHONE_INDEX, false /*need validation*/,
+                            false /*isForPerformance*/));
+            long delayMs = -1;
+            // Check if already scheduled one with that combination of extras.
+            if (eventExtras.targetPhoneId != targetPhoneId
+                    || eventExtras.needValidation != needValidation
+                    || eventExtras.isForPerformance != isForPerformance) {
+                eventExtras =
+                        new StabilityEventExtra(targetPhoneId, isForPerformance, needValidation);
+
+                // Reset with new timer.
+                delayMs = isForPerformance
+                        ? mAutoDataSwitchPerformanceStabilityTimeThreshold
+                        : mAutoDataSwitchAvailabilityStabilityTimeThreshold;
+                scheduleEventWithTimer(EVENT_STABILITY_CHECK_PASSED, eventExtras, delayMs);
+            }
+            log("startStabilityCheck: "
+                    + (delayMs != -1 ? "scheduling " : "already scheduled ")
+                    + eventExtras);
+        } else if (!hasEqualMessages(EVENT_STABILITY_CHECK_PASSED, combinationIdentifier)) {
+            removeMessages(EVENT_STABILITY_CHECK_PASSED);
+            sendMessageDelayed(obtainMessage(EVENT_STABILITY_CHECK_PASSED, targetPhoneId,
                             needValidation ? 1 : 0,
                             combinationIdentifier),
                     mAutoDataSwitchAvailabilityStabilityTimeThreshold);
+            log("startStabilityCheck: targetPhoneId=" + targetPhoneId
+                    + " isForPerformance=" + isForPerformance
+                    + " needValidation=" + needValidation);
+        }
+    }
+
+    /**
+     * Use when need to schedule with timer. Short timer uses handler, while the longer timer uses
+     * alarm manager to account for real time elapse.
+     *
+     * @param event The event.
+     * @param extras Any extra data associated with the event.
+     * @param delayMs The delayed interval in ms.
+     */
+    private void scheduleEventWithTimer(int event, @NonNull Object extras, long delayMs) {
+        // Get singleton alarm listener.
+        mEventsToAlarmListener.putIfAbsent(event, () -> sendEmptyMessage(event));
+        AlarmManager.OnAlarmListener listener = mEventsToAlarmListener.get(event);
+
+        // Cancel any existing.
+        removeMessages(event);
+        mAlarmManager.cancel(listener);
+        // Override with new extras.
+        mScheduledEventsToExtras.put(event, extras);
+        // Reset timer.
+        if (delayMs <= RETRY_LONG_DELAY_TIMER_THRESHOLD_MILLIS) {
+            // Use handler for short timer.
+            sendEmptyMessageDelayed(event, delayMs);
+        } else {
+            // Not using setWhileIdle because it can wait util the next time the device wakes up to
+            // save power.
+            // If another evaluation is processed before the alarm fires,
+            // this timer is restarted (AlarmManager using the same listener resets the
+            // timer).
+            mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME,
+                    SystemClock.elapsedRealtime() + delayMs,
+                    LOG_TAG /*debug tag*/, listener, this);
         }
     }
 
@@ -906,7 +1049,19 @@
     private void cancelAnyPendingSwitch() {
         mSelectedTargetPhoneId = INVALID_PHONE_INDEX;
         resetFailedCount();
-        removeMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE);
+        if (sFeatureFlags.autoDataSwitchRatSs()) {
+            if (mScheduledEventsToExtras.containsKey(EVENT_STABILITY_CHECK_PASSED)) {
+                if (mEventsToAlarmListener.containsKey(EVENT_STABILITY_CHECK_PASSED)) {
+                    mAlarmManager.cancel(mEventsToAlarmListener.get(EVENT_STABILITY_CHECK_PASSED));
+                } else {
+                    loge("cancelAnyPendingSwitch: EVENT_STABILITY_CHECK_PASSED listener is null");
+                }
+                removeMessages(EVENT_STABILITY_CHECK_PASSED);
+                mScheduledEventsToExtras.remove(EVENT_STABILITY_CHECK_PASSED);
+            }
+        } else {
+            removeMessages(EVENT_STABILITY_CHECK_PASSED);
+        }
         mPhoneSwitcherCallback.onRequireCancelAnyPendingAutoSwitchValidation();
     }
 
@@ -920,8 +1075,6 @@
                 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;
diff --git a/src/java/com/android/internal/telephony/data/DataConfigManager.java b/src/java/com/android/internal/telephony/data/DataConfigManager.java
index ea7b1da..90743f8 100644
--- a/src/java/com/android/internal/telephony/data/DataConfigManager.java
+++ b/src/java/com/android/internal/telephony/data/DataConfigManager.java
@@ -870,6 +870,14 @@
     }
 
     /**
+     * @return the data limit in bytes that can be used for esim bootstrap usage.
+     */
+    public long getEsimBootStrapMaxDataLimitBytes() {
+        return mResources.getInteger(
+                com.android.internal.R.integer.config_esim_bootstrap_data_limit_bytes);
+    }
+
+    /**
      * Update the TCP buffer sizes from the resource overlays.
      */
     private void updateTcpBuffers() {
@@ -1061,6 +1069,16 @@
     }
 
     /**
+     * @return Time threshold in ms to define a internet connection performance status to be stable
+     * (e.g. LTE + 4 signal strength, UMTS + 2 signal strength), while -1 indicates
+     * auto switch feature based on RAT/SS is disabled.
+     */
+    public long getAutoDataSwitchPerformanceStabilityTimeThreshold() {
+        return mResources.getInteger(com.android.internal.R.integer
+                .auto_data_switch_performance_stability_time_threshold_millis);
+    }
+
+    /**
      * Get the TCP config string, used by {@link LinkProperties#setTcpBufferSizes(String)}.
      * The config string will have the following form, with values in bytes:
      * "read_min,read_default,read_max,write_min,write_default,write_max"
@@ -1398,6 +1416,17 @@
     }
 
     /**
+     * @return Indicating whether the retry timer from setup data call response for data throttling
+     * should be honored for emergency network request. By default this is off, meaning emergency
+     * network requests will ignore the previous retry timer passed in from setup data call
+     * response.
+     */
+    public boolean shouldHonorRetryTimerForEmergencyNetworkRequest() {
+        return mResources.getBoolean(
+                com.android.internal.R.bool.config_honor_data_retry_timer_for_emergency_network);
+    }
+
+    /**
      * Log debug messages.
      * @param s debug messages
      */
@@ -1440,6 +1469,8 @@
         pw.increaseIndent();
         mDataHandoverRetryRules.forEach(pw::println);
         pw.decreaseIndent();
+        pw.println("shouldHonorRetryTimerForEmergencyNetworkRequest="
+                + shouldHonorRetryTimerForEmergencyNetworkRequest());
         pw.println("mSetupDataCallAnomalyReport=" + mSetupDataCallAnomalyReportThreshold);
         pw.println("mNetworkUnwantedAnomalyReport=" + mNetworkUnwantedAnomalyReportThreshold);
         pw.println("mImsReleaseRequestAnomalyReport=" + mImsReleaseRequestAnomalyReportThreshold);
@@ -1456,6 +1487,8 @@
                 + Arrays.toString(value)));
         pw.println("getAutoDataSwitchAvailabilityStabilityTimeThreshold="
                 + getAutoDataSwitchAvailabilityStabilityTimeThreshold());
+        pw.println("getAutoDataSwitchPerformanceStabilityTimeThreshold="
+                + getAutoDataSwitchPerformanceStabilityTimeThreshold());
         pw.println("getAutoDataSwitchValidationMaxRetry=" + getAutoDataSwitchValidationMaxRetry());
         pw.decreaseIndent();
         pw.println("Metered APN types=" + mMeteredApnTypes.stream()
diff --git a/src/java/com/android/internal/telephony/data/DataEvaluation.java b/src/java/com/android/internal/telephony/data/DataEvaluation.java
index 6858c6a..3d10e9c 100644
--- a/src/java/com/android/internal/telephony/data/DataEvaluation.java
+++ b/src/java/com/android/internal/telephony/data/DataEvaluation.java
@@ -257,7 +257,9 @@
         /** Tracking area code changed. */
         TAC_CHANGED(true),
         /** Unsatisfied network request detached. */
-        UNSATISFIED_REQUEST_DETACHED(true);
+        UNSATISFIED_REQUEST_DETACHED(true),
+        /** track bootstrap sim data usage */
+        CHECK_DATA_USAGE(false);
 
         /**
          * {@code true} if the evaluation is due to environmental changes (i.e. SIM removal,
@@ -342,7 +344,9 @@
         /** Data enabled settings are not ready. */
         DATA_SETTINGS_NOT_READY(true),
         /** Handover max retry stopped but network is not on the preferred transport. */
-        HANDOVER_RETRY_STOPPED(true);
+        HANDOVER_RETRY_STOPPED(true),
+        /** BootStrap sim data limit reached. */
+        DATA_LIMIT_REACHED(true);
 
         private final boolean mIsHardReason;
 
diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java
index 6ae861c..c1e6155 100644
--- a/src/java/com/android/internal/telephony/data/DataNetwork.java
+++ b/src/java/com/android/internal/telephony/data/DataNetwork.java
@@ -122,6 +122,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -312,6 +313,7 @@
                     TEAR_DOWN_REASON_ILLEGAL_STATE,
                     TEAR_DOWN_REASON_ONLY_ALLOWED_SINGLE_NETWORK,
                     TEAR_DOWN_REASON_PREFERRED_DATA_SWITCHED,
+                    TEAR_DOWN_REASON_DATA_LIMIT_REACHED,
             })
     public @interface TearDownReason {}
 
@@ -408,6 +410,9 @@
     /** Data network tear down due to preferred data switched to another phone. */
     public static final int TEAR_DOWN_REASON_PREFERRED_DATA_SWITCHED = 30;
 
+    /** Data network tear down due to bootstrap sim data limit reached. */
+    public static final int TEAR_DOWN_REASON_DATA_LIMIT_REACHED = 31;
+
     //********************************************************************************************//
     // WHENEVER ADD A NEW TEAR DOWN REASON, PLEASE UPDATE DataDeactivateReasonEnum in enums.proto //
     //********************************************************************************************//
@@ -917,6 +922,14 @@
          */
         public abstract void onRetryUnsatisfiedNetworkRequest(
                 @NonNull TelephonyNetworkRequest networkRequest);
+
+        /**
+         * Called when QosBearerSessions bearer changed, which indicates VoNr or VoLte calls.
+         *
+         * @param qosBearerSessions The current qosBearerSessions.
+         */
+        public abstract void onQosSessionsChanged(
+                @NonNull List<QosBearerSession> qosBearerSessions);
     }
 
     /**
@@ -2672,6 +2685,12 @@
 
         mDefaultQos = response.getDefaultQos();
 
+
+        Set<QosBearerSession> newSessions = new HashSet<>(response.getQosBearerSessions());
+        if (newSessions.size() != mQosBearerSessions.size()
+                || !newSessions.containsAll(mQosBearerSessions)) {
+            mDataNetworkCallback.onQosSessionsChanged(response.getQosBearerSessions());
+        }
         mQosBearerSessions.clear();
         mQosBearerSessions.addAll(response.getQosBearerSessions());
         if (mQosCallbackTracker != null) {
@@ -3757,6 +3776,8 @@
                 return "TEAR_DOWN_REASON_ONLY_ALLOWED_SINGLE_NETWORK";
             case TEAR_DOWN_REASON_PREFERRED_DATA_SWITCHED:
                 return "TEAR_DOWN_REASON_PREFERRED_DATA_SWITCHED";
+            case TEAR_DOWN_REASON_DATA_LIMIT_REACHED:
+                return "TEAR_DOWN_REASON_DATA_LIMIT_REACHED";
             default:
                 return "UNKNOWN(" + reason + ")";
         }
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index 645e8bd..3ca28a3 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -20,6 +20,8 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -29,6 +31,7 @@
 import android.net.NetworkPolicyManager;
 import android.net.NetworkPolicyManager.SubscriptionCallback;
 import android.net.NetworkRequest;
+import android.net.NetworkTemplate;
 import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Handler;
@@ -66,6 +69,7 @@
 import android.telephony.data.DataCallResponse.LinkStatus;
 import android.telephony.data.DataProfile;
 import android.telephony.data.DataServiceCallback;
+import android.telephony.data.QosBearerSession;
 import android.telephony.ims.ImsException;
 import android.telephony.ims.ImsManager;
 import android.telephony.ims.ImsReasonInfo;
@@ -238,6 +242,18 @@
     private static final long REEVALUATE_UNSATISFIED_NETWORK_REQUESTS_AFTER_DETACHED_DELAY_MILLIS =
             TimeUnit.SECONDS.toMillis(1);
 
+    /**
+     * The delay in milliseconds to re-evaluate existing data networks for bootstrap sim data usage
+     * limit.
+     */
+    private static final long REEVALUATE_BOOTSTRAP_SIM_DATA_USAGE_MILLIS =
+            TimeUnit.SECONDS.toMillis(60);
+
+    /**
+     * bootstrap sim total data usage bytes
+     */
+    private long mBootStrapSimTotalDataUsageBytes = 0L;
+
     private final Phone mPhone;
     private final String mLogTag;
     private final LocalLog mLocalLog = new LocalLog(128);
@@ -632,6 +648,13 @@
          * @param simState The current SIM state
          */
         public void onSimStateChanged(@SimState int simState) {}
+
+        /**
+         * Called when QosBearerSessions changed.
+         *
+         * @param qosBearerSessions The latest QOS bearer sessions.
+         */
+        public void onQosSessionsChanged(@NonNull List<QosBearerSession> qosBearerSessions) {}
     }
 
     /**
@@ -1505,11 +1528,27 @@
 
         // Bypass all checks for emergency network request.
         if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)) {
-            evaluation.addDataAllowedReason(DataAllowedReason.EMERGENCY_REQUEST);
-            evaluation.setCandidateDataProfile(mDataProfileManager.getDataProfileForNetworkRequest(
+            DataProfile emergencyProfile = mDataProfileManager.getDataProfileForNetworkRequest(
                     networkRequest, getDataNetworkType(transport),
                     mServiceState.isUsingNonTerrestrialNetwork(),
-                    isEsimBootStrapProvisioningActivated(), true));
+                    isEsimBootStrapProvisioningActivated(), true);
+
+            // Check if the profile is being throttled.
+            if (mDataConfigManager.shouldHonorRetryTimerForEmergencyNetworkRequest()
+                    && emergencyProfile != null
+                    && mDataRetryManager.isDataProfileThrottled(emergencyProfile, transport)) {
+                evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_THROTTLED);
+                log("Emergency network request is throttled by the previous setup data "
+                            + "call response.");
+                log(evaluation.toString());
+                networkRequest.setEvaluation(evaluation);
+                return evaluation;
+            }
+
+            evaluation.addDataAllowedReason(DataAllowedReason.EMERGENCY_REQUEST);
+            if (emergencyProfile != null) {
+                evaluation.setCandidateDataProfile(emergencyProfile);
+            }
             networkRequest.setEvaluation(evaluation);
             log(evaluation.toString());
             return evaluation;
@@ -1685,7 +1724,14 @@
         }
 
         if (!evaluation.containsDisallowedReasons()) {
-            evaluation.setCandidateDataProfile(dataProfile);
+            if (transport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+                    && isEsimBootStrapProvisioningActivated()
+                    && isEsimBootStrapMaxDataLimitReached()) {
+                log("BootStrap Sim Data Usage limit reached");
+                evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_LIMIT_REACHED);
+            } else {
+                evaluation.setCandidateDataProfile(dataProfile);
+            }
         }
 
         networkRequest.setEvaluation(evaluation);
@@ -1702,6 +1748,61 @@
     }
 
     /**
+     * This method
+     *  - At evaluation network request and evaluation data network determines, if
+     *    bootstrap sim current data usage reached bootstrap sim max data limit allowed set
+     *    at {@link DataConfigManager#getEsimBootStrapMaxDataLimitBytes()}
+     *  - Query the current data usage at {@link #getDataUsage()}
+     *
+     * @return true, if bootstrap sim data limit is reached
+     *         else false, if bootstrap sim max data limit allowed set is -1(Unlimited) or current
+     *         bootstrap sim total data usage is less than bootstrap sim max data limit allowed.
+     *
+     */
+    private boolean isEsimBootStrapMaxDataLimitReached() {
+        long esimBootStrapMaxDataLimitBytes =
+                mDataConfigManager.getEsimBootStrapMaxDataLimitBytes();
+
+        if (esimBootStrapMaxDataLimitBytes < 0L) {
+            return false;
+        }
+
+        log("current bootstrap sim data Usage: " + mBootStrapSimTotalDataUsageBytes);
+        if (mBootStrapSimTotalDataUsageBytes >= esimBootStrapMaxDataLimitBytes) {
+            return true;
+        } else {
+            mBootStrapSimTotalDataUsageBytes = getDataUsage();
+            return mBootStrapSimTotalDataUsageBytes >= esimBootStrapMaxDataLimitBytes;
+        }
+    }
+
+    /**
+     * Query network usage statistics summaries based on {@link
+     * NetworkStatsManager#querySummaryForDevice(NetworkTemplate, long, long)}
+     *
+     * @return Data usage in bytes for the connected networks related to the current subscription
+     */
+    private long getDataUsage() {
+        NetworkStatsManager networkStatsManager =
+                        mPhone.getContext().getSystemService(NetworkStatsManager.class);
+
+        if (networkStatsManager != null) {
+            final NetworkTemplate.Builder builder =
+                    new NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE);
+            final String subscriberId = mPhone.getSubscriberId();
+
+            if (!TextUtils.isEmpty(subscriberId)) {
+                builder.setSubscriberIds(Set.of(subscriberId));
+                NetworkTemplate template = builder.build();
+                final NetworkStats.Bucket ret = networkStatsManager
+                        .querySummaryForDevice(template, 0L, System.currentTimeMillis());
+                return ret.getRxBytes() + ret.getTxBytes();
+            }
+        }
+        return 0L;
+    }
+
+    /**
      * @return The grouped unsatisfied network requests. The network requests that have the same
      * network capabilities is grouped into one {@link NetworkRequestList}.
      */
@@ -1787,6 +1888,25 @@
             evaluation.addDataDisallowedReason(DataDisallowedReason.SERVICE_OPTION_NOT_SUPPORTED);
         }
 
+        // Check whether data limit reached for bootstrap sim, else re-evaluate based on the timer
+        // set.
+        if (isEsimBootStrapProvisioningActivated()
+                && dataNetwork.getTransport() == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
+            if (isEsimBootStrapMaxDataLimitReached()) {
+                log("BootStrap Sim Data Usage limit reached");
+                evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_LIMIT_REACHED);
+            } else {
+                if (!hasMessages(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS)) {
+                    sendMessageDelayed(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
+                            DataEvaluationReason.CHECK_DATA_USAGE),
+                            REEVALUATE_BOOTSTRAP_SIM_DATA_USAGE_MILLIS);
+                } else {
+                    log("skip scheduling evaluating existing data networks since already"
+                            + "scheduled");
+                }
+            }
+        }
+
         // Check if there are other network that has higher priority, and only single data network
         // is allowed.
         if (isOnlySingleDataNetworkAllowed(dataNetwork.getTransport())
@@ -2169,6 +2289,8 @@
                     return DataNetwork.TEAR_DOWN_REASON_ONLY_ALLOWED_SINGLE_NETWORK;
                 case HANDOVER_RETRY_STOPPED:
                     return DataNetwork.TEAR_DOWN_REASON_HANDOVER_FAILED;
+                case DATA_LIMIT_REACHED:
+                    return DataNetwork.TEAR_DOWN_REASON_DATA_LIMIT_REACHED;
             }
         }
         return DataNetwork.TEAR_DOWN_REASON_NONE;
@@ -2675,6 +2797,14 @@
                         DataNetworkController.this.onRetryUnsatisfiedNetworkRequest(
                                 networkRequest);
                     }
+
+                    @Override
+                    public void onQosSessionsChanged(
+                            @NonNull List<QosBearerSession> qosBearerSessions) {
+                        mDataNetworkControllerCallbacks.forEach(
+                                callback -> callback.invokeFromExecutor(() ->
+                                        callback.onQosSessionsChanged(qosBearerSessions)));
+                    }
                 }
         ));
         if (!mAnyDataNetworkExisting) {
@@ -2797,6 +2927,12 @@
                     + TelephonyUtils.dataStateToString(mImsDataNetworkState) + " to CONNECTED.");
             mImsDataNetworkState = TelephonyManager.DATA_CONNECTED;
         }
+
+        if (isEsimBootStrapProvisioningActivated()) {
+            sendMessageDelayed(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
+                    DataEvaluationReason.CHECK_DATA_USAGE),
+                    REEVALUATE_BOOTSTRAP_SIM_DATA_USAGE_MILLIS);
+        }
     }
 
     /**
@@ -4052,6 +4188,7 @@
                 .map(TelephonyManager::getNetworkTypeName).collect(Collectors.joining(",")));
         pw.println("mImsThrottleCounter=" + mImsThrottleCounter);
         pw.println("mNetworkUnwantedCounter=" + mNetworkUnwantedCounter);
+        pw.println("mBootStrapSimTotalDataUsageBytes=" + mBootStrapSimTotalDataUsageBytes);
         pw.println("Local logs:");
         pw.increaseIndent();
         mLocalLog.dump(fd, pw, args);
diff --git a/src/java/com/android/internal/telephony/data/DataProfileManager.java b/src/java/com/android/internal/telephony/data/DataProfileManager.java
index b4055a3..51fc71b 100644
--- a/src/java/com/android/internal/telephony/data/DataProfileManager.java
+++ b/src/java/com/android/internal/telephony/data/DataProfileManager.java
@@ -34,6 +34,7 @@
 import android.telephony.Annotation.NetworkType;
 import android.telephony.AnomalyReporter;
 import android.telephony.CarrierConfigManager;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.TelephonyManager.SimState;
@@ -286,7 +287,8 @@
                     log("Added " + dataProfile);
 
                     isInternetSupported |= apn.canHandleType(ApnSetting.TYPE_DEFAULT);
-                    if (mDataConfigManager.isApnConfigAnomalyReportEnabled()) {
+                    if (mDataConfigManager.isApnConfigAnomalyReportEnabled()
+                            && apn.getEditedStatus() == Telephony.Carriers.UNEDITED) {
                         checkApnSetting(apn);
                     }
                 }
@@ -818,8 +820,13 @@
                 })
                 .collect(Collectors.toList());
         if (dataProfiles.size() == 0) {
+            String ntnReason = "";
+            if (mFeatureFlags.carrierEnabledSatelliteFlag()) {
+                ntnReason = " and infrastructure for "
+                        + NetworkRegistrationInfo.isNonTerrestrialNetworkToString(isNtn);
+            }
             log("Can't find any data profile for network type "
-                    + TelephonyManager.getNetworkTypeName(networkType));
+                    + TelephonyManager.getNetworkTypeName(networkType) + ntnReason);
             return null;
         }
 
diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
index c66aebc..e3eed00 100644
--- a/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
@@ -33,7 +33,7 @@
 import android.telephony.DomainSelectionService;
 import android.telephony.DomainSelectionService.EmergencyScanType;
 import android.telephony.DomainSelector;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.data.ApnSetting;
 import android.util.LocalLog;
@@ -66,6 +66,8 @@
     protected static final int EVENT_QUALIFIED_NETWORKS_CHANGED = 2;
     protected static final int EVENT_SERVICE_CONNECTED = 3;
     protected static final int EVENT_SERVICE_BINDING_TIMEOUT = 4;
+    protected static final int EVENT_RESET_NETWORK_SCAN_DONE = 5;
+    protected static final int EVENT_LAST = EVENT_RESET_NETWORK_SCAN_DONE;
 
     private static final int DEFAULT_BIND_RETRY_TIMEOUT_MS = 4 * 1000;
 
@@ -73,6 +75,7 @@
     private static final int STATUS_DOMAIN_SELECTED  = 1 << 1;
     private static final int STATUS_WAIT_BINDING     = 1 << 2;
     private static final int STATUS_WAIT_SCAN_RESULT = 1 << 3;
+    private static final int STATUS_WAIT_RESET_SCAN_RESULT = 1 << 4;
 
     /** Callback to receive responses from DomainSelectionConnection. */
     public interface DomainSelectionConnectionCallback {
@@ -85,6 +88,16 @@
         void onSelectionTerminated(@DisconnectCauses int cause);
     }
 
+    private static class ScanRequest {
+        final int[] mPreferredNetworks;
+        final int mScanType;
+
+        ScanRequest(int[] preferredNetworks, int scanType) {
+            mPreferredNetworks = preferredNetworks;
+            mScanType = scanType;
+        }
+    }
+
     /**
      * A wrapper class for {@link ITransportSelectorCallback} interface.
      */
@@ -95,7 +108,7 @@
                 mDomainSelector = selector;
                 if (checkState(STATUS_DISPOSED)) {
                     try {
-                        selector.cancelSelection();
+                        selector.finishSelection();
                     } catch (RemoteException e) {
                         // ignore exception
                     }
@@ -117,20 +130,6 @@
         }
 
         @Override
-        public @NonNull IWwanSelectorCallback onWwanSelected() {
-            synchronized (mLock) {
-                if (mWwanSelectorCallback == null) {
-                    mWwanSelectorCallback = new WwanSelectorCallbackAdaptor();
-                }
-                if (checkState(STATUS_DISPOSED)) {
-                    return mWwanSelectorCallback;
-                }
-                DomainSelectionConnection.this.onWwanSelected();
-                return mWwanSelectorCallback;
-            }
-        }
-
-        @Override
         public void onWwanSelectedAsync(@NonNull final ITransportSelectorResultCallback cb) {
             synchronized (mLock) {
                 if (checkState(STATUS_DISPOSED)) {
@@ -139,25 +138,41 @@
                 if (mWwanSelectorCallback == null) {
                     mWwanSelectorCallback = new WwanSelectorCallbackAdaptor();
                 }
-                initHandler();
-                mHandler.post(() -> {
-                    synchronized (mLock) {
-                        if (checkState(STATUS_DISPOSED)) {
-                            return;
+                if (mIsTestMode || !mIsEmergency
+                        || (mSelectorType != DomainSelectionService.SELECTOR_TYPE_CALLING)) {
+                    initHandler();
+                    mHandler.post(() -> {
+                        onWwanSelectedAsyncInternal(cb);
+                    });
+                } else {
+                    Thread workerThread = new Thread(new Runnable() {
+                        @Override
+                        public void run() {
+                            onWwanSelectedAsyncInternal(cb);
                         }
-                        DomainSelectionConnection.this.onWwanSelected();
-                    }
-                    try {
-                        cb.onCompleted(mWwanSelectorCallback);
-                    } catch (RemoteException e) {
-                        loge("onWwanSelectedAsync executor exception=" + e);
-                        synchronized (mLock) {
-                            // Since remote service is not available,
-                            // wait for binding or timeout.
-                            waitForServiceBinding(null);
-                        }
-                    }
-                });
+                    });
+                    workerThread.start();
+                }
+            }
+        }
+
+        private void onWwanSelectedAsyncInternal(
+                @NonNull final ITransportSelectorResultCallback cb) {
+            synchronized (mLock) {
+                if (checkState(STATUS_DISPOSED)) {
+                    return;
+                }
+            }
+            DomainSelectionConnection.this.onWwanSelected();
+            try {
+                cb.onCompleted(mWwanSelectorCallback);
+            } catch (RemoteException e) {
+                loge("onWwanSelectedAsync executor exception=" + e);
+                synchronized (mLock) {
+                    // Since remote service is not available,
+                    // wait for binding or timeout.
+                    waitForServiceBinding(null);
+                }
             }
         }
 
@@ -180,7 +195,8 @@
         @Override
         public void onRequestEmergencyNetworkScan(
                 @NonNull @RadioAccessNetworkType int[] preferredNetworks,
-                @EmergencyScanType int scanType, @NonNull IWwanSelectorResultCallback cb) {
+                @EmergencyScanType int scanType, boolean resetScan,
+                @NonNull IWwanSelectorResultCallback cb) {
             synchronized (mLock) {
                 if (checkState(STATUS_DISPOSED)) {
                     return;
@@ -190,7 +206,7 @@
                 mHandler.post(() -> {
                     synchronized (mLock) {
                         DomainSelectionConnection.this.onRequestEmergencyNetworkScan(
-                                preferredNetworks, scanType);
+                                preferredNetworks, scanType, resetScan);
                     }
                 });
             }
@@ -232,7 +248,7 @@
             switch (msg.what) {
                 case EVENT_EMERGENCY_NETWORK_SCAN_RESULT:
                     ar = (AsyncResult) msg.obj;
-                    EmergencyRegResult regResult = (EmergencyRegResult) ar.result;
+                    EmergencyRegistrationResult regResult = (EmergencyRegistrationResult) ar.result;
                     if (DBG) logd("EVENT_EMERGENCY_NETWORK_SCAN_RESULT result=" + regResult);
                     synchronized (mLock) {
                         clearState(STATUS_WAIT_SCAN_RESULT);
@@ -275,6 +291,17 @@
                         }
                     }
                     break;
+                case EVENT_RESET_NETWORK_SCAN_DONE:
+                    synchronized (mLock) {
+                        clearState(STATUS_WAIT_RESET_SCAN_RESULT);
+                        if (checkState(STATUS_DISPOSED)
+                                || (mPendingScanRequest == null)) {
+                            return;
+                        }
+                        onRequestEmergencyNetworkScan(mPendingScanRequest.mPreferredNetworks,
+                                mPendingScanRequest.mScanType, false);
+                    }
+                    break;
                 default:
                     loge("handleMessage unexpected msg=" + msg.what);
                     break;
@@ -320,6 +347,10 @@
 
     private @NonNull AndroidFuture<Integer> mOnComplete;
 
+    private @Nullable ScanRequest mPendingScanRequest;
+
+    private boolean mIsTestMode = false;
+
     /**
      * Creates an instance.
      *
@@ -364,7 +395,7 @@
      *
      * @return The {@link IWwanSelectorResultCallback} interface.
      */
-    public @Nullable IWwanSelectorResultCallback getEmergencyRegResultCallback() {
+    public @Nullable IWwanSelectorResultCallback getWwanSelectorResultCallback() {
         return mResultCallback;
     }
 
@@ -449,10 +480,11 @@
      *
      * @param preferredNetworks The ordered list of preferred networks to scan.
      * @param scanType Indicates the scan preference, such as full service or limited service.
+     * @param resetScan Indicates that the previous scan result shall be reset before scanning.
      */
     public void onRequestEmergencyNetworkScan(
             @NonNull @RadioAccessNetworkType int[] preferredNetworks,
-            @EmergencyScanType int scanType) {
+            @EmergencyScanType int scanType, boolean resetScan) {
         // Can be overridden if required
 
         synchronized (mLock) {
@@ -464,6 +496,29 @@
                 return;
             }
 
+            if (checkState(STATUS_WAIT_RESET_SCAN_RESULT)) {
+                if (mPendingScanRequest != null) {
+                    /* Consecutive scan requests without cancellation is not an expected use case.
+                     * DomainSelector should cancel the previous request or wait for the result
+                     * before requesting a new scan.*/
+                    logi("onRequestEmergencyNetworkScan consecutive scan requests");
+                    return;
+                } else {
+                    // The reset has not been completed.
+                    // case1) Long delay in cancelEmergencyNetworkScan by modem.
+                    // case2) A consecutive scan requests with short interval from DomainSelector.
+                    logi("onRequestEmergencyNetworkScan reset not completed");
+                }
+                mPendingScanRequest = new ScanRequest(preferredNetworks, scanType);
+                return;
+            } else if (resetScan) {
+                setState(STATUS_WAIT_RESET_SCAN_RESULT);
+                mPendingScanRequest = new ScanRequest(preferredNetworks, scanType);
+                mPhone.cancelEmergencyNetworkScan(resetScan,
+                        mHandler.obtainMessage(EVENT_RESET_NETWORK_SCAN_DONE));
+                return;
+            }
+
             if (!mRegisteredRegistrant) {
                 mPhone.registerForEmergencyNetworkScan(mHandler,
                         EVENT_EMERGENCY_NETWORK_SCAN_RESULT, null);
@@ -471,6 +526,7 @@
             }
             setState(STATUS_WAIT_SCAN_RESULT);
             mPhone.triggerEmergencyNetworkScan(preferredNetworks, scanType, null);
+            mPendingScanRequest = null;
         }
     }
 
@@ -506,6 +562,7 @@
     }
 
     private void onCancel(boolean resetScan) {
+        mPendingScanRequest = null;
         if (checkState(STATUS_WAIT_SCAN_RESULT)) {
             clearState(STATUS_WAIT_SCAN_RESULT);
             mPhone.cancelEmergencyNetworkScan(resetScan, null);
@@ -517,17 +574,7 @@
      * to clean up all ongoing operations with the framework.
      */
     public void cancelSelection() {
-        synchronized (mLock) {
-            try {
-                if (mDomainSelector != null) {
-                    mDomainSelector.cancelSelection();
-                }
-            } catch (RemoteException e) {
-                loge("cancelSelection exception=" + e);
-            } finally {
-                dispose();
-            }
-        }
+        finishSelection();
     }
 
     /**
@@ -732,6 +779,16 @@
     }
 
     /**
+     * Set whether it is unit test or not.
+     *
+     * @param testMode Indicates whether it is unit test or not.
+     */
+    @VisibleForTesting
+    public void setTestMode(boolean testMode) {
+        mIsTestMode = testMode;
+    }
+
+    /**
      * Dumps local log.
      */
     public void dump(@NonNull PrintWriter printWriter) {
diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java
index 6d64a31..ee8517d 100644
--- a/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java
+++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java
@@ -101,6 +101,7 @@
 
     private ExponentialBackoff mBackoff;
     private boolean mBackoffStarted = false;
+    private boolean mUnbind = false;
 
     // Retry the bind to the DomainSelectionService that has died after mBindRetry timeout.
     private Runnable mRestartBindingRunnable = new Runnable() {
@@ -470,12 +471,14 @@
      */
     public boolean bind(@NonNull ComponentName componentName) {
         mComponentName = componentName;
+        mUnbind = false;
         return bind();
     }
 
     private boolean bind() {
         logd("bind isBindingOrBound=" + mIsBound);
         synchronized (mLock) {
+            if (mUnbind) return false;
             if (!mIsBound) {
                 mIsBound = true;
                 Intent serviceIntent = new Intent(DomainSelectionService.SERVICE_INTERFACE)
@@ -512,6 +515,7 @@
      */
     public void unbind() {
         synchronized (mLock) {
+            mUnbind = true;
             stopBackoffTimer();
             mIsBound = false;
             setServiceController(null);
@@ -548,7 +552,8 @@
     }
 
     private void notifyBindFailure() {
-        logi("notifyBindFailure " + mBackoffStarted);
+        logi("notifyBindFailure started=" + mBackoffStarted + ", unbind=" + mUnbind);
+        if (mUnbind) return;
         if (mBackoffStarted) {
             mBackoff.notifyFailed();
         } else {
diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java
index 8a0dcf5..410f89b 100644
--- a/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java
+++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java
@@ -24,6 +24,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.SystemProperties;
 import android.telephony.DomainSelectionService;
 import android.text.TextUtils;
 import android.util.IndentingPrintWriter;
@@ -34,6 +35,7 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.flags.Flags;
+import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -48,6 +50,10 @@
     @VisibleForTesting
     protected static final String PACKAGE_NAME_NONE = "none";
     private static final String TAG = DomainSelectionResolver.class.getSimpleName();
+    private static final boolean DBG = TelephonyUtils.IS_DEBUGGABLE;
+    /** For test purpose only with userdebug release */
+    private static final String PROP_DISABLE_DOMAIN_SELECTION =
+            "telephony.test.disable_domain_selection";
     private static DomainSelectionResolver sInstance = null;
 
     /**
@@ -132,6 +138,10 @@
      *         {@code false} otherwise.
      */
     public boolean isDomainSelectionSupported() {
+        if (DBG && SystemProperties.getBoolean(PROP_DISABLE_DOMAIN_SELECTION, false)) {
+            logi("Disabled for test");
+            return false;
+        }
         return mDefaultComponentName != null && PhoneFactory.getDefaultPhone()
                 .getHalVersion(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1);
     }
@@ -198,7 +208,6 @@
      * @return {@code true} if the requested operation is successfully done,
      *         {@code false} otherwise.
      */
-    @VisibleForTesting
     public boolean setDomainSelectionServiceOverride(@NonNull ComponentName componentName) {
         if (mController == null) {
             logd("Controller is not initialized.");
@@ -224,7 +233,6 @@
      * @return {@code true} if the requested operation is successfully done,
      *         {@code false} otherwise.
      */
-    @VisibleForTesting
     public boolean clearDomainSelectionServiceOverride() {
         if (mController == null) {
             logd("Controller is not initialized.");
diff --git a/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java
index af4b03a..cb4ddab 100644
--- a/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java
@@ -28,12 +28,14 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.Uri;
+import android.telecom.PhoneAccount;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.Annotation.DisconnectCauses;
 import android.telephony.Annotation.NetCapability;
 import android.telephony.DomainSelectionService;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.data.ApnSetting;
 import android.telephony.ims.ImsReasonInfo;
@@ -107,7 +109,7 @@
     /** {@inheritDoc} */
     @Override
     public void onWwanSelected() {
-        mEmergencyStateTracker.onEmergencyTransportChanged(
+        mEmergencyStateTracker.onEmergencyTransportChangedAndWait(
                 EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN);
     }
 
@@ -202,6 +204,7 @@
      * @param exited {@code true} if the request caused the device to move out of airplane mode.
      * @param callId The call identifier.
      * @param number The dialed number.
+     * @param isTest Indicates it's a test emergency number.
      * @param callFailCause The reason why the last CS attempt failed.
      * @param imsReasonInfo The reason why the last PS attempt failed.
      * @param emergencyRegResult The current registration result for emergency services.
@@ -209,20 +212,21 @@
      */
     public static @NonNull DomainSelectionService.SelectionAttributes getSelectionAttributes(
             int slotId, int subId, boolean exited,
-            @NonNull String callId, @NonNull String number, int callFailCause,
-            @Nullable ImsReasonInfo imsReasonInfo,
-            @Nullable EmergencyRegResult emergencyRegResult) {
+            @NonNull String callId, @NonNull String number, boolean isTest,
+            int callFailCause, @Nullable ImsReasonInfo imsReasonInfo,
+            @Nullable EmergencyRegistrationResult emergencyRegResult) {
         DomainSelectionService.SelectionAttributes.Builder builder =
                 new DomainSelectionService.SelectionAttributes.Builder(
                         slotId, subId, SELECTOR_TYPE_CALLING)
                 .setEmergency(true)
+                .setTestEmergencyNumber(isTest)
                 .setExitedFromAirplaneMode(exited)
                 .setCallId(callId)
-                .setNumber(number)
+                .setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null))
                 .setCsDisconnectCause(callFailCause);
 
         if (imsReasonInfo != null) builder.setPsDisconnectCause(imsReasonInfo);
-        if (emergencyRegResult != null) builder.setEmergencyRegResult(emergencyRegResult);
+        if (emergencyRegResult != null) builder.setEmergencyRegistrationResult(emergencyRegResult);
 
         return builder.build();
     }
@@ -234,13 +238,15 @@
         if (attr == null) return null;
         DomainSelectionService.SelectionAttributes.Builder builder =
                 new DomainSelectionService.SelectionAttributes.Builder(
-                        attr.getSlotId(), attr.getSubId(), SELECTOR_TYPE_CALLING)
+                        attr.getSlotIndex(), attr.getSubscriptionId(), SELECTOR_TYPE_CALLING)
                 .setCallId(attr.getCallId())
-                .setNumber(attr.getNumber())
+                .setAddress(attr.getAddress())
                 .setVideoCall(attr.isVideoCall())
                 .setEmergency(true)
+                .setTestEmergencyNumber(attr.isTestEmergencyNumber())
                 .setExitedFromAirplaneMode(attr.isExitedFromAirplaneMode())
-                .setEmergencyRegResult(new EmergencyRegResult(AccessNetworkType.UNKNOWN,
+                .setEmergencyRegistrationResult(
+                        new EmergencyRegistrationResult(AccessNetworkType.UNKNOWN,
                         NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN,
                         NetworkRegistrationInfo.DOMAIN_UNKNOWN, false, false, 0, 0,
                         "", "", ""));
diff --git a/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java
index 0532a05..0fd9201 100644
--- a/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java
@@ -20,6 +20,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.Uri;
+import android.telecom.PhoneAccount;
 import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
 import android.telephony.Annotation.DisconnectCauses;
 import android.telephony.DisconnectCause;
@@ -78,7 +80,7 @@
     /** {@inheritDoc} */
     @Override
     public void onRequestEmergencyNetworkScan(@RadioAccessNetworkType int[] preferredNetworks,
-            @EmergencyScanType int scanType) {
+            @EmergencyScanType int scanType, boolean resetScan) {
         // Not expected with normal calling.
         // Override to prevent abnormal behavior.
     }
@@ -119,7 +121,7 @@
                         slotId, subId, SELECTOR_TYPE_CALLING)
                         .setEmergency(false)
                         .setCallId(callId)
-                        .setNumber(number)
+                        .setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null))
                         .setCsDisconnectCause(callFailCause)
                         .setVideoCall(isVideoCall);
 
diff --git a/src/java/com/android/internal/telephony/domainselection/OWNERS b/src/java/com/android/internal/telephony/domainselection/OWNERS
index b9112be..2a76770 100644
--- a/src/java/com/android/internal/telephony/domainselection/OWNERS
+++ b/src/java/com/android/internal/telephony/domainselection/OWNERS
@@ -6,3 +6,4 @@
 mkoon@google.com
 seheele@google.com
 radhikaagrawal@google.com
+jdyou@google.com
diff --git a/src/java/com/android/internal/telephony/domainselection/SmsDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/SmsDomainSelectionConnection.java
index 36a7b17..b3f4924 100644
--- a/src/java/com/android/internal/telephony/domainselection/SmsDomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/SmsDomainSelectionConnection.java
@@ -53,17 +53,6 @@
         if (mCallback != null) mCallback.onSelectionTerminated(cause);
     }
 
-    @Override
-    public void finishSelection() {
-        CompletableFuture<Integer> future = getCompletableFuture();
-
-        if (future != null && !future.isDone()) {
-            cancelSelection();
-        } else {
-            super.finishSelection();
-        }
-    }
-
     /**
      * Requests a domain selection for SMS.
      *
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
index f3c0a6c..a35cccf 100644
--- a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
@@ -35,6 +35,7 @@
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.PowerManager;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
@@ -43,7 +44,7 @@
 import android.telephony.Annotation.DisconnectCauses;
 import android.telephony.CarrierConfigManager;
 import android.telephony.DisconnectCause;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PreciseDataConnectionState;
 import android.telephony.ServiceState;
@@ -84,13 +85,17 @@
      * Timeout before we continue with the emergency call without waiting for DDS switch response
      * from the modem.
      */
-    private static final int DEFAULT_DATA_SWITCH_TIMEOUT_MS = 1000;
+    private static final int DEFAULT_DATA_SWITCH_TIMEOUT_MS = 1 * 1000;
+    @VisibleForTesting
+    public static final int DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS = 3 * 1000;
     /** Default value for if Emergency Callback Mode is supported. */
     private static final boolean DEFAULT_EMERGENCY_CALLBACK_MODE_SUPPORTED = true;
     /** Default Emergency Callback Mode exit timeout value. */
     private static final long DEFAULT_ECM_EXIT_TIMEOUT_MS = 300000;
     private static final int DEFAULT_EPDN_DISCONNECTION_TIMEOUT_MS = 500;
 
+    private static final int DEFAULT_TRANSPORT_CHANGE_TIMEOUT_MS = 1 * 1000;
+
     /** The emergency types used when setting the emergency mode on modem. */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "EMERGENCY_TYPE_",
@@ -117,7 +122,7 @@
     @EmergencyConstants.EmergencyMode
     private int mEmergencyMode = MODE_EMERGENCY_NONE;
     private boolean mWasEmergencyModeSetOnModem;
-    private EmergencyRegResult mLastEmergencyRegResult;
+    private EmergencyRegistrationResult mLastEmergencyRegistrationResult;
     private boolean mIsEmergencyModeInProgress;
     private boolean mIsEmergencyCallStartedDuringEmergencySms;
 
@@ -126,13 +131,13 @@
     // A runnable which is used to automatically exit from Ecm after a period of time.
     private final Runnable mExitEcmRunnable = this::exitEmergencyCallbackMode;
     // Tracks emergency calls by callId that have reached {@link Call.State#ACTIVE}.
-    private final Set<String> mActiveEmergencyCalls = new ArraySet<>();
+    private final Set<android.telecom.Connection> mActiveEmergencyCalls = new ArraySet<>();
     private Phone mPhoneToExit;
     private int mPdnDisconnectionTimeoutMs = DEFAULT_EPDN_DISCONNECTION_TIMEOUT_MS;
     private final Object mLock = new Object();
     private Phone mPhone;
-    // Tracks ongoing emergency callId to handle a second emergency call
-    private String mOngoingCallId;
+    // Tracks ongoing emergency connection to handle a second emergency call
+    private android.telecom.Connection mOngoingConnection;
     // Domain of the active emergency call. Assuming here that there will only be one domain active.
     private int mEmergencyCallDomain = NetworkRegistrationInfo.DOMAIN_UNKNOWN;
     private CompletableFuture<Integer> mCallEmergencyModeFuture;
@@ -148,6 +153,8 @@
     private CompletableFuture<Integer> mSmsEmergencyModeFuture;
     private boolean mIsTestEmergencyNumberForSms;
 
+    private CompletableFuture<Boolean> mEmergencyTransportChangedFuture;
+
     private final android.util.ArrayMap<Integer, Boolean> mNoSimEcbmSupported =
             new android.util.ArrayMap<>();
     private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener =
@@ -286,14 +293,18 @@
                     Rlog.v(TAG, "MSG_SET_EMERGENCY_MODE_DONE for "
                             + emergencyTypeToString(emergencyType));
                     if (ar.exception == null) {
-                        mLastEmergencyRegResult = (EmergencyRegResult) ar.result;
+                        mLastEmergencyRegistrationResult = (EmergencyRegistrationResult) ar.result;
                     } else {
-                        mLastEmergencyRegResult = null;
-                        Rlog.w(TAG, "LastEmergencyRegResult not set. AsyncResult.exception: "
+                        mLastEmergencyRegistrationResult = null;
+                        Rlog.w(TAG,
+                                "LastEmergencyRegistrationResult not set. AsyncResult.exception: "
                                 + ar.exception);
                     }
                     setEmergencyModeInProgress(false);
 
+                    // Transport changed from WLAN to WWAN or CALLBACK to WWAN
+                    maybeNotifyTransportChangeCompleted(emergencyType, false);
+
                     if (emergencyType == EMERGENCY_TYPE_CALL) {
                         setIsInEmergencyCall(true);
                         completeEmergencyMode(emergencyType);
@@ -496,24 +507,25 @@
      * Handles turning on radio and switching DDS.
      *
      * @param phone                 the {@code Phone} on which to process the emergency call.
-     * @param callId                the call id on which to process the emergency call.
+     * @param c                     the {@code Connection} on which to process the emergency call.
      * @param isTestEmergencyNumber whether this is a test emergency number.
      * @return a {@code CompletableFuture} that results in {@code DisconnectCause.NOT_DISCONNECTED}
      *         if emergency call successfully started.
      */
     public CompletableFuture<Integer> startEmergencyCall(@NonNull Phone phone,
-            @NonNull String callId, boolean isTestEmergencyNumber) {
-        Rlog.i(TAG, "startEmergencyCall: phoneId=" + phone.getPhoneId() + ", callId=" + callId);
+            @NonNull android.telecom.Connection c, boolean isTestEmergencyNumber) {
+        Rlog.i(TAG, "startEmergencyCall: phoneId=" + phone.getPhoneId()
+                + ", callId=" + c.getTelecomCallId());
 
         if (mPhone != null) {
             // Create new future to return as to not interfere with any uncompleted futures.
             // Case1) When 2nd emergency call is initiated during an active call on the same phone.
             // Case2) While the device is in ECBM, an emergency call is initiated on the same phone.
             if (isSamePhone(mPhone, phone) && (!mActiveEmergencyCalls.isEmpty() || isInEcm())) {
-                mOngoingCallId = callId;
+                mOngoingConnection = c;
                 mIsTestEmergencyNumber = isTestEmergencyNumber;
                 // Ensure that domain selector requests scan.
-                mLastEmergencyRegResult = new EmergencyRegResult(
+                mLastEmergencyRegistrationResult = new EmergencyRegistrationResult(
                         AccessNetworkConstants.AccessNetworkType.UNKNOWN,
                         NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN,
                         NetworkRegistrationInfo.DOMAIN_UNKNOWN, false, false, 0, 0, "", "", "");
@@ -545,13 +557,13 @@
             }
 
             mPhone = phone;
-            mOngoingCallId = callId;
+            mOngoingConnection = c;
             mIsTestEmergencyNumber = isTestEmergencyNumber;
             return mCallEmergencyModeFuture;
         }
 
         mPhone = phone;
-        mOngoingCallId = callId;
+        mOngoingConnection = c;
         mIsTestEmergencyNumber = isTestEmergencyNumber;
         turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL, mIsTestEmergencyNumber);
         return mCallEmergencyModeFuture;
@@ -564,13 +576,13 @@
      * Enter ECM only once all active emergency calls have ended. If a call never reached
      * {@link Call.State#ACTIVE}, then no need to enter ECM.
      *
-     * @param callId the call id on which to end the emergency call.
+     * @param c the emergency call disconnected.
      */
-    public void endCall(@NonNull String callId) {
-        boolean wasActive = mActiveEmergencyCalls.remove(callId);
+    public void endCall(@NonNull android.telecom.Connection c) {
+        boolean wasActive = mActiveEmergencyCalls.remove(c);
 
-        if (Objects.equals(mOngoingCallId, callId)) {
-            mOngoingCallId = null;
+        if (Objects.equals(mOngoingConnection, c)) {
+            mOngoingConnection = null;
             mOngoingCallProperties = 0;
         }
 
@@ -578,11 +590,11 @@
                 && isEmergencyCallbackModeSupported()) {
             enterEmergencyCallbackMode();
 
-            if (mOngoingCallId == null) {
+            if (mOngoingConnection == null) {
                 mIsEmergencyCallStartedDuringEmergencySms = false;
                 mCallEmergencyModeFuture = null;
             }
-        } else if (mOngoingCallId == null) {
+        } else if (mOngoingConnection == null) {
             if (isInEcm()) {
                 mIsEmergencyCallStartedDuringEmergencySms = false;
                 mCallEmergencyModeFuture = null;
@@ -596,6 +608,9 @@
                 clearEmergencyCallInfo();
             }
         }
+
+        // Release any blocked thread immediately
+        maybeNotifyTransportChangeCompleted(EMERGENCY_TYPE_CALL, true);
     }
 
     private void clearEmergencyCallInfo() {
@@ -603,7 +618,7 @@
         mIsTestEmergencyNumber = false;
         mIsEmergencyCallStartedDuringEmergencySms = false;
         mCallEmergencyModeFuture = null;
-        mOngoingCallId = null;
+        mOngoingConnection = null;
         mOngoingCallProperties = 0;
         mPhone = null;
     }
@@ -616,10 +631,10 @@
                 Rlog.e(TAG, "DDS Switch failed.");
             }
             // Once radio is on and DDS switched, must call setEmergencyMode() before selecting
-            // emergency domain. EmergencyRegResult is required to determine domain and this is the
-            // only API that can receive it before starting domain selection. Once domain selection
-            // is finished, the actual emergency mode will be set when onEmergencyTransportChanged()
-            // is called.
+            // emergency domain. EmergencyRegistrationResult is required to determine domain and
+            // this is the only API that can receive it before starting domain selection.
+            // Once domain selection is finished, the actual emergency mode will be set when
+            // onEmergencyTransportChanged() is called.
             setEmergencyMode(phone, emergencyType, MODE_EMERGENCY_WWAN,
                     MSG_SET_EMERGENCY_MODE_DONE);
         });
@@ -639,14 +654,15 @@
                 + emergencyTypeToString(emergencyType));
 
         if (mEmergencyMode == mode) {
+            // Initial transport selection of DomainSelector
+            maybeNotifyTransportChangeCompleted(emergencyType, false);
             return;
         }
         mEmergencyMode = mode;
         setEmergencyModeInProgress(true);
 
         Message m = mHandler.obtainMessage(msg, Integer.valueOf(emergencyType));
-        if ((mIsTestEmergencyNumber && emergencyType == EMERGENCY_TYPE_CALL)
-                || (mIsTestEmergencyNumberForSms && emergencyType == EMERGENCY_TYPE_SMS)) {
+        if (mIsTestEmergencyNumberForSms && emergencyType == EMERGENCY_TYPE_SMS) {
             Rlog.d(TAG, "TestEmergencyNumber for " + emergencyTypeToString(emergencyType)
                     + ": Skipping setting emergency mode on modem.");
             // Send back a response for the command, but with null information
@@ -808,9 +824,72 @@
         }
     }
 
-    /** Returns last {@link EmergencyRegResult} as set by {@code setEmergencyMode()}. */
-    public EmergencyRegResult getEmergencyRegResult() {
-        return mLastEmergencyRegResult;
+    /** Returns last {@link EmergencyRegistrationResult} as set by {@code setEmergencyMode()}. */
+    public EmergencyRegistrationResult getEmergencyRegistrationResult() {
+        return mLastEmergencyRegistrationResult;
+    }
+
+    private void waitForTransportChangeCompleted(CompletableFuture<Boolean> future) {
+        if (future != null) {
+            synchronized (future) {
+                if ((mEmergencyMode == MODE_EMERGENCY_NONE)
+                        || mHandler.getLooper().isCurrentThread()) {
+                    // Do not block the Handler's thread
+                    return;
+                }
+                long now = SystemClock.elapsedRealtime();
+                long deadline = now + DEFAULT_TRANSPORT_CHANGE_TIMEOUT_MS;
+                // Guard with while loop to handle spurious wakeups
+                while (!future.isDone() && now < deadline) {
+                    try {
+                        future.wait(deadline - now);
+                    } catch (Exception e) {
+                        Rlog.e(TAG, "waitForTransportChangeCompleted wait e=" + e);
+                    }
+                    now = SystemClock.elapsedRealtime();
+                }
+            }
+        }
+    }
+
+    private void maybeNotifyTransportChangeCompleted(@EmergencyType int emergencyType,
+            boolean enforced) {
+        if (emergencyType != EMERGENCY_TYPE_CALL) {
+            // It's not for the emergency call
+            return;
+        }
+        CompletableFuture<Boolean> future = mEmergencyTransportChangedFuture;
+        if (future != null) {
+            synchronized (future) {
+                if (!future.isDone()
+                        && ((!isEmergencyModeInProgress() && mEmergencyMode == MODE_EMERGENCY_WWAN)
+                                || enforced)) {
+                    future.complete(Boolean.TRUE);
+                    future.notifyAll();
+                }
+            }
+        }
+    }
+
+    /**
+     * Handles emergency transport change by setting new emergency mode.
+     *
+     * @param emergencyType the emergency type to identify an emergency call or SMS
+     * @param mode the new emergency mode
+     */
+    public void onEmergencyTransportChangedAndWait(@EmergencyType int emergencyType,
+            @EmergencyConstants.EmergencyMode int mode) {
+        // Wait for the completion of setting MODE_EMERGENCY_WWAN only for emergency calls
+        if (emergencyType == EMERGENCY_TYPE_CALL && mode == MODE_EMERGENCY_WWAN) {
+            CompletableFuture<Boolean> future = new CompletableFuture<>();
+            synchronized (future) {
+                mEmergencyTransportChangedFuture = future;
+                onEmergencyTransportChanged(emergencyType, mode);
+                waitForTransportChangeCompleted(future);
+            }
+            return;
+        }
+        onEmergencyTransportChanged(emergencyType, mode);
     }
 
     /**
@@ -842,10 +921,10 @@
     /**
      * Notify the tracker that the emergency call domain has been updated.
      * @param phoneType The new PHONE_TYPE_* of the call.
-     * @param callId The ID of the call
+     * @param c The connection of the call
      */
-    public void onEmergencyCallDomainUpdated(int phoneType, String callId) {
-        Rlog.d(TAG, "domain update for callId: " + callId);
+    public void onEmergencyCallDomainUpdated(int phoneType, android.telecom.Connection c) {
+        Rlog.d(TAG, "domain update for callId: " + c.getTelecomCallId());
         int domain = -1;
         switch(phoneType) {
             case (PhoneConstants.PHONE_TYPE_CDMA_LTE):
@@ -873,13 +952,13 @@
      * Handles emergency call state change.
      *
      * @param state the new call state
-     * @param callId the callId whose state has changed
+     * @param c the call whose state has changed
      */
-    public void onEmergencyCallStateChanged(Call.State state, String callId) {
+    public void onEmergencyCallStateChanged(Call.State state, android.telecom.Connection c) {
         if (state == Call.State.ACTIVE) {
-            mActiveEmergencyCalls.add(callId);
-            if (Objects.equals(mOngoingCallId, callId)) {
-                Rlog.i(TAG, "call connected " + callId);
+            mActiveEmergencyCalls.add(c);
+            if (Objects.equals(mOngoingConnection, c)) {
+                Rlog.i(TAG, "call connected " + c.getTelecomCallId());
                 if (mPhone != null
                         && isVoWiFi(mOngoingCallProperties)
                         && mEmergencyMode == EmergencyConstants.MODE_EMERGENCY_WLAN) {
@@ -894,10 +973,10 @@
      * Handles the change of emergency call properties.
      *
      * @param properties the new call properties.
-     * @param callId the callId whose state has changed.
+     * @param c the call whose state has changed.
      */
-    public void onEmergencyCallPropertiesChanged(int properties, String callId) {
-        if (Objects.equals(mOngoingCallId, callId)) {
+    public void onEmergencyCallPropertiesChanged(int properties, android.telecom.Connection c) {
+        if (Objects.equals(mOngoingConnection, c)) {
             mOngoingCallProperties = properties;
         }
     }
@@ -1160,7 +1239,7 @@
             if (isInEcm()) {
                 // When the emergency mode is not in MODE_EMERGENCY_CALLBACK,
                 // it needs to notify the emergency callback mode to modem.
-                if (mActiveEmergencyCalls.isEmpty() && mOngoingCallId == null) {
+                if (mActiveEmergencyCalls.isEmpty() && mOngoingConnection == null) {
                     setEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, MODE_EMERGENCY_CALLBACK,
                             MSG_SET_EMERGENCY_CALLBACK_MODE_DONE);
                 }
@@ -1203,8 +1282,8 @@
      *
      * <p>
      * Once radio is on and DDS switched, must call setEmergencyMode() before completing the future
-     * and selecting emergency domain. EmergencyRegResult is required to determine domain and
-     * setEmergencyMode() is the only API that can receive it before starting domain selection.
+     * and selecting emergency domain. EmergencyRegistrationResult is required to determine domain
+     * and setEmergencyMode() is the only API that can receive it before starting domain selection.
      * Once domain selection is finished, the actual emergency mode will be set when
      * onEmergencyTransportChanged() is called.
      *
@@ -1227,6 +1306,11 @@
                 mRadioOnHelper = new RadioOnHelper(mContext);
             }
 
+            final Phone phoneForEmergency = phone;
+            final android.telecom.Connection expectedConnection = mOngoingConnection;
+            final int waitForInServiceTimeout =
+                    needToTurnOnRadio ? DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS : 0;
+            Rlog.i(TAG, "turnOnRadioAndSwitchDds: timeout=" + waitForInServiceTimeout);
             mRadioOnHelper.triggerRadioOnAndListen(new RadioOnStateListener.Callback() {
                 @Override
                 public void onComplete(RadioOnStateListener listener, boolean isRadioReady) {
@@ -1241,25 +1325,34 @@
                             completeEmergencyMode(emergencyType, DisconnectCause.POWER_OFF);
                         }
                     } else {
+                        if (!Objects.equals(mOngoingConnection, expectedConnection)) {
+                            Rlog.i(TAG, "onComplete "
+                                    + expectedConnection.getTelecomCallId() + " canceled.");
+                            return;
+                        }
                         switchDdsAndSetEmergencyMode(phone, emergencyType);
                     }
                 }
 
                 @Override
                 public boolean isOkToCall(Phone phone, int serviceState, boolean imsVoiceCapable) {
-                    // We currently only look to make sure that the radio is on before dialing. We
-                    // should be able to make emergency calls at any time after the radio has been
-                    // powered on and isn't in the UNAVAILABLE state, even if it is reporting the
-                    // OUT_OF_SERVICE state.
+                    // Wait for normal service state or timeout if required.
+                    if (phone == phoneForEmergency
+                            && waitForInServiceTimeout > 0
+                            && !isNetworkRegistered(phone)) {
+                        return false;
+                    }
                     return phone.getServiceStateTracker().isRadioOn()
                             && !satelliteController.isSatelliteEnabled();
                 }
 
                 @Override
                 public boolean onTimeout(Phone phone, int serviceState, boolean imsVoiceCapable) {
-                    return true;
+                    // onTimeout shall be called only with the Phone for emergency
+                    return phone.getServiceStateTracker().isRadioOn()
+                            && !satelliteController.isSatelliteEnabled();
                 }
-            }, !isTestEmergencyNumber, phone, isTestEmergencyNumber, 0);
+            }, !isTestEmergencyNumber, phone, isTestEmergencyNumber, waitForInServiceTimeout);
         } else {
             switchDdsAndSetEmergencyMode(phone, emergencyType);
         }
@@ -1412,6 +1505,27 @@
                 || phone.getServiceState().isEmergencyOnly();
     }
 
+    private static boolean isNetworkRegistered(Phone phone) {
+        ServiceState ss = phone.getServiceStateTracker().getServiceState();
+        if (ss != null) {
+            NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
+                    NetworkRegistrationInfo.DOMAIN_PS,
+                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+            if (nri != null && nri.isNetworkRegistered()) {
+                // PS is IN_SERVICE state.
+                return true;
+            }
+            nri = ss.getNetworkRegistrationInfo(
+                    NetworkRegistrationInfo.DOMAIN_CS,
+                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+            if (nri != null && nri.isNetworkRegistered()) {
+                // CS is IN_SERVICE state.
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Checks whether both {@code Phone}s are same or not.
      */
diff --git a/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java b/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
index 9c4ebfa..384112d 100644
--- a/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
+++ b/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
@@ -96,7 +96,7 @@
                 continue;
             }
 
-            int timeoutCallbackInterval = (forEmergencyCall && phone == phoneForEmergencyCall)
+            int timeoutCallbackInterval = (phone == phoneForEmergencyCall)
                     ? emergencyTimeoutIntervalMillis : 0;
             mInProgressListeners.add(mListeners.get(i));
             mListeners.get(i).waitForRadioOn(phone, this, forEmergencyCall, forEmergencyCall
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
index c417a34..3c444ef 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
@@ -47,6 +47,7 @@
 import android.service.euicc.IEraseSubscriptionsCallback;
 import android.service.euicc.IEuiccService;
 import android.service.euicc.IEuiccServiceDumpResultCallback;
+import android.service.euicc.IGetAvailableMemoryInBytesCallback;
 import android.service.euicc.IGetDefaultDownloadableSubscriptionListCallback;
 import android.service.euicc.IGetDownloadableSubscriptionMetadataCallback;
 import android.service.euicc.IGetEidCallback;
@@ -155,6 +156,7 @@
     private static final int CMD_START_OTA_IF_NECESSARY = 112;
     private static final int CMD_ERASE_SUBSCRIPTIONS_WITH_OPTIONS = 113;
     private static final int CMD_DUMP_EUICC_SERVICE = 114;
+    private static final int CMD_GET_AVAILABLE_MEMORY_IN_BYTES = 115;
 
     private static boolean isEuiccCommand(int what) {
         return what >= CMD_GET_EID;
@@ -208,6 +210,13 @@
         void onGetEidComplete(String eid);
     }
 
+    /** Callback class for {@link #getAvailableMemoryInBytes}. */
+    @VisibleForTesting(visibility = PACKAGE)
+    public interface GetAvailableMemoryInBytesCommandCallback extends BaseEuiccCommandCallback {
+        /** Called when the available memory in bytes lookup has completed. */
+        void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes);
+    }
+
     /** Callback class for {@link #getOtaStatus}. */
     @VisibleForTesting(visibility = PACKAGE)
     public interface GetOtaStatusCommandCallback extends BaseEuiccCommandCallback {
@@ -436,6 +445,13 @@
         sendMessage(CMD_GET_EID, cardId, 0 /* arg2 */, callback);
     }
 
+    /** Asynchronously fetch the available memory in bytes. */
+    @VisibleForTesting(visibility = PACKAGE)
+    public void getAvailableMemoryInBytes(
+            int cardId, GetAvailableMemoryInBytesCommandCallback callback) {
+        sendMessage(CMD_GET_AVAILABLE_MEMORY_IN_BYTES, cardId, 0 /* arg2 */, callback);
+    }
+
     /** Asynchronously get OTA status. */
     @VisibleForTesting(visibility = PACKAGE)
     public void getOtaStatus(int cardId, GetOtaStatusCommandCallback callback) {
@@ -760,6 +776,22 @@
                                     });
                             break;
                         }
+                        case CMD_GET_AVAILABLE_MEMORY_IN_BYTES: {
+                            mEuiccService.getAvailableMemoryInBytes(slotId,
+                                    new IGetAvailableMemoryInBytesCallback.Stub() {
+                                        @Override
+                                        public void onSuccess(long availableMemoryInBytes) {
+                                            sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> {
+                                                ((GetAvailableMemoryInBytesCommandCallback)
+                                                        callback)
+                                                        .onGetAvailableMemoryInBytesComplete(
+                                                                availableMemoryInBytes);
+                                                onCommandEnd(callback);
+                                            });
+                                        }
+                                    });
+                            break;
+                        }
                         case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA: {
                             GetMetadataRequest request = (GetMetadataRequest) message.obj;
                             mEuiccService.getDownloadableSubscriptionMetadata(slotId,
@@ -1036,6 +1068,7 @@
             case CMD_GET_OTA_STATUS:
             case CMD_START_OTA_IF_NECESSARY:
             case CMD_DUMP_EUICC_SERVICE:
+            case CMD_GET_AVAILABLE_MEMORY_IN_BYTES:
                 return (BaseEuiccCommandCallback) message.obj;
             case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA:
                 return ((GetMetadataRequest) message.obj).mCallback;
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccController.java b/src/java/com/android/internal/telephony/euicc/EuiccController.java
index a24ab43..6dd6f65 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java
@@ -24,6 +24,8 @@
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.flags.Flags;
 import android.app.compat.CompatChanges;
 import android.content.ComponentName;
 import android.content.Context;
@@ -33,6 +35,8 @@
 import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.service.euicc.DownloadSubscriptionResult;
 import android.service.euicc.EuiccService;
@@ -54,6 +58,7 @@
 import android.telephony.euicc.EuiccManager;
 import android.telephony.euicc.EuiccManager.OtaStatus;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Pair;
@@ -72,13 +77,16 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import java.util.Stack;
 import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
 
 /** Backing implementation of {@link android.telephony.euicc.EuiccManager}. */
 public class EuiccController extends IEuiccController.Stub {
@@ -121,6 +129,7 @@
     // the phone process, 3) values are updated remotely by server flags.
     private List<String> mSupportedCountries;
     private List<String> mUnsupportedCountries;
+    private List<Integer> mPsimConversionSupportedCarrierIds;
 
     /** Initialize the instance. Should only be called once. */
     public static EuiccController init(Context context, FeatureFlags featureFlags) {
@@ -251,6 +260,36 @@
     }
 
     /**
+     * Return the available memory in bytes of the eUICC.
+     *
+     * <p>For API simplicity, this call blocks until completion; while it requires an IPC to load,
+     * that IPC should generally be fast, and the available memory shouldn't be needed in the normal
+     * course of operation.
+     */
+    @Override
+    public long getAvailableMemoryInBytes(int cardId, String callingPackage) {
+        boolean callerCanReadPhoneStatePrivileged = callerCanReadPhoneStatePrivileged();
+        boolean callerCanReadPhoneState = callerCanReadPhoneState();
+        mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
+        long token = Binder.clearCallingIdentity();
+        try {
+            if (!callerCanReadPhoneStatePrivileged
+                    && !callerCanReadPhoneState
+                    && !canManageSubscriptionOnTargetSim(
+                            cardId, callingPackage, false, TelephonyManager.INVALID_PORT_INDEX)) {
+                throw new SecurityException(
+                        "Must have READ_PHONE_STATE permission or READ_PRIVILEGED_PHONE_STATE"
+                            + " permission or carrier privileges to read the available memory for"
+                            + "cardId="
+                                + cardId);
+            }
+            return blockingGetAvailableMemoryInBytesFromEuiccService(cardId);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
      * Return the current status of OTA update.
      *
      * <p>For API simplicity, this call blocks until completion; while it requires an IPC to load,
@@ -577,6 +616,19 @@
             boolean switchAfterDownload, String callingPackage, boolean forceDeactivateSim,
             Bundle resolvedBundle, PendingIntent callbackIntent) {
         boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions();
+        boolean callerCanDownloadAdminManagedSubscription =
+                Flags.esimManagementEnabled()
+                        && callerCanManageDevicePolicyManagedSubscriptions(callingPackage);
+        if (Flags.esimManagementEnabled()) {
+            if (mContext
+                    .getSystemService(UserManager.class)
+                    .hasUserRestriction(UserManager.DISALLOW_SIM_GLOBALLY)
+                    && !callerCanDownloadAdminManagedSubscription) {
+                // Only admin managed subscriptions are allowed, but the caller is not authorised to
+                // download admin managed subscriptions. Abort.
+                throw new SecurityException("Caller is not authorized to download subscriptions");
+            }
+        }
         mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
         // Don't try to resolve the port index for apps which are not targeting on T for backward
         // compatibility. instead always use default port 0.
@@ -594,18 +646,27 @@
                 isConsentNeededToResolvePortIndex = (portIndex
                         == TelephonyManager.INVALID_PORT_INDEX);
             }
+            // Caller has admin privileges if they can download admin managed subscription,
+            // and are not switching the subscription after download (admins cannot silently
+            // enable the subscription).
+            boolean hasAdminPrivileges =
+                    callerCanDownloadAdminManagedSubscription && !switchAfterDownload;
             Log.d(TAG, " downloadSubscription cardId: " + cardId + " switchAfterDownload: "
                     + switchAfterDownload + " portIndex: " + portIndex
                     + " forceDeactivateSim: " + forceDeactivateSim + " callingPackage: "
                     + callingPackage
                     + " isConsentNeededToResolvePortIndex: " + isConsentNeededToResolvePortIndex
-                    + " shouldResolvePortIndex:" + shouldResolvePortIndex);
-            if (!isConsentNeededToResolvePortIndex && callerCanWriteEmbeddedSubscriptions) {
+                    + " shouldResolvePortIndex:" + shouldResolvePortIndex
+                    + " hasAdminPrivileges:" + hasAdminPrivileges);
+            if (!isConsentNeededToResolvePortIndex
+                    && (callerCanWriteEmbeddedSubscriptions
+                                    || hasAdminPrivileges)) {
                 // With WRITE_EMBEDDED_SUBSCRIPTIONS, we can skip profile-specific permission checks
                 // and move straight to the profile download.
                 downloadSubscriptionPrivileged(cardId, portIndex, token, subscription,
                         switchAfterDownload, forceDeactivateSim, callingPackage, resolvedBundle,
-                        callbackIntent);
+                        callbackIntent, callerCanDownloadAdminManagedSubscription,
+                        getCurrentEmbeddedSubscriptionIds(cardId));
                 return;
             }
 
@@ -741,11 +802,30 @@
                         true /* withUserConsent */, portIndex));
     }
 
+    void downloadSubscriptionPrivileged(int cardId, int portIndex, final long callingToken,
+            DownloadableSubscription subscription, boolean switchAfterDownload,
+            boolean forceDeactivateSim, final String callingPackage,
+            Bundle resolvedBundle, final PendingIntent callbackIntent) {
+        downloadSubscriptionPrivileged(
+                cardId,
+                portIndex,
+                callingToken,
+                subscription,
+                switchAfterDownload,
+                forceDeactivateSim,
+                callingPackage,
+                resolvedBundle,
+                callbackIntent,
+                false /* markAsOwnedByAdmin */,
+                new ArraySet<>() /* existingSubscriptions */);
+    }
+
     // Continue to download subscription without checking anything.
     void downloadSubscriptionPrivileged(int cardId, int portIndex, final long callingToken,
             DownloadableSubscription subscription, boolean switchAfterDownload,
             boolean forceDeactivateSim, final String callingPackage, Bundle resolvedBundle,
-            final PendingIntent callbackIntent) {
+            final PendingIntent callbackIntent, boolean markAsOwnedByAdmin,
+            Set<Integer> existingSubscriptions) {
         mConnector.downloadSubscription(
                 cardId,
                 portIndex,
@@ -774,7 +854,13 @@
                                     // Since we're not switching, nothing will trigger a
                                     // subscription list refresh on its own, so request one here.
                                     refreshSubscriptionsAndSendResult(
-                                            callbackIntent, resultCode, extrasIntent);
+                                            callbackIntent,
+                                            resultCode,
+                                            extrasIntent,
+                                            markAsOwnedByAdmin,
+                                            callingPackage,
+                                            cardId,
+                                            existingSubscriptions);
                                     return;
                                 }
                                 break;
@@ -976,6 +1062,9 @@
     public void deleteSubscription(int cardId, int subscriptionId, String callingPackage,
             PendingIntent callbackIntent) {
         boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions();
+        boolean callerIsAdmin =
+                Flags.esimManagementEnabled()
+                        && callerCanManageDevicePolicyManagedSubscriptions(callingPackage);
         mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
 
         long token = Binder.clearCallingIdentity();
@@ -986,13 +1075,14 @@
                 sendResult(callbackIntent, ERROR, null /* extrasIntent */);
                 return;
             }
-
+            boolean adminOwned = callerIsAdmin && sub.getGroupOwner().equals(callingPackage);
             // For both single active SIM device and multi-active SIM device, if the caller is
             // system or the caller manage the target subscription, we let it continue. This is
             // because deleting subscription won't change status of any other subscriptions.
             if (!callerCanWriteEmbeddedSubscriptions
-                    && !mSubscriptionManager.canManageSubscription(sub, callingPackage)) {
-                Log.e(TAG, "No permissions: " + subscriptionId);
+                    && !mSubscriptionManager.canManageSubscription(sub, callingPackage)
+                    && !adminOwned) {
+                Log.e(TAG, "No permissions: " + subscriptionId + " adminOwned=" + adminOwned);
                 sendResult(callbackIntent, ERROR, null /* extrasIntent */);
                 return;
             }
@@ -1610,9 +1700,43 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     public void refreshSubscriptionsAndSendResult(
             PendingIntent callbackIntent, int resultCode, Intent extrasIntent) {
+        refreshSubscriptionsAndSendResult(
+                callbackIntent,
+                resultCode,
+                extrasIntent,
+                /* isCallerAdmin= */ false,
+                /* callingPackage= */ "",
+                /* cardId */ -1,
+                /* subscriptionsBefore= */ new ArraySet<>());
+    }
+
+    /** Refresh the embedded subscription list and dispatch the given result upon completion. */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public void refreshSubscriptionsAndSendResult(
+            PendingIntent callbackIntent,
+            int resultCode,
+            Intent extrasIntent,
+            boolean isCallerAdmin,
+            String callingPackage,
+            int cardId,
+            Set<Integer> subscriptionsBefore) {
         SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions(
                 List.of(mTelephonyManager.getCardIdForDefaultEuicc()),
-                () -> sendResult(callbackIntent, resultCode, extrasIntent));
+                () -> {
+                    if (Flags.esimManagementEnabled() && isCallerAdmin) {
+                        // Mark the newly downloaded subscriptions as being owned by an admin so
+                        // that actions for that subscription can be restricted,
+                        // and the admin is limited to effecting only these subscriptions.
+                        Set<Integer> subscriptionsAfter = getCurrentEmbeddedSubscriptionIds(cardId);
+                        subscriptionsAfter.removeAll(subscriptionsBefore);
+                        for (int subId: subscriptionsAfter) {
+                            SubscriptionManagerService
+                                    .getInstance().setGroupOwner(subId, callingPackage);
+                        }
+                    }
+                    sendResult(callbackIntent, resultCode, extrasIntent);
+                });
+
     }
 
     /** Dispatch the given callback intent with the given result code and data. */
@@ -1728,6 +1852,23 @@
         return null;
     }
 
+    private Set<Integer> getCurrentEmbeddedSubscriptionIds(int cardId) {
+        if (!Flags.esimManagementEnabled()) {
+            return new ArraySet<>();
+        }
+        List<SubscriptionInfo> subscriptionInfos =
+                mSubscriptionManager.getAvailableSubscriptionInfoList();
+        int subCount = (subscriptionInfos != null) ? subscriptionInfos.size() : 0;
+        Set<Integer> currentEmbeddedSubscriptionIds = new ArraySet<>();
+        for (int i = 0; i < subCount; i++) {
+            SubscriptionInfo subscriptionInfo = subscriptionInfos.get(i);
+            if (subscriptionInfo.isEmbedded() && subscriptionInfo.getCardId() == cardId) {
+                currentEmbeddedSubscriptionIds.add(subscriptionInfo.getSubscriptionId());
+            }
+        }
+        return currentEmbeddedSubscriptionIds;
+    }
+
     @Nullable
     private String blockingGetEidFromEuiccService(int cardId) {
         CountDownLatch latch = new CountDownLatch(1);
@@ -1747,6 +1888,27 @@
         return awaitResult(latch, eidRef);
     }
 
+    private long blockingGetAvailableMemoryInBytesFromEuiccService(int cardId) {
+        CountDownLatch latch = new CountDownLatch(1);
+        AtomicReference<Long> memoryRef =
+                new AtomicReference<>(EuiccManager.EUICC_MEMORY_FIELD_UNAVAILABLE);
+        mConnector.getAvailableMemoryInBytes(
+                cardId,
+                new EuiccConnector.GetAvailableMemoryInBytesCommandCallback() {
+                    @Override
+                    public void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes) {
+                        memoryRef.set(availableMemoryInBytes);
+                        latch.countDown();
+                    }
+
+                    @Override
+                    public void onEuiccServiceUnavailable() {
+                        latch.countDown();
+                    }
+                });
+        return awaitResult(latch, memoryRef);
+    }
+
     private @OtaStatus int blockingGetOtaStatusFromEuiccService(int cardId) {
         CountDownLatch latch = new CountDownLatch(1);
         AtomicReference<Integer> statusRef =
@@ -1954,12 +2116,49 @@
                 == PackageManager.PERMISSION_GRANTED;
     }
 
+    private boolean callerCanReadPhoneState() {
+        return mContext.checkCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
     private boolean callerCanWriteEmbeddedSubscriptions() {
         return mContext.checkCallingOrSelfPermission(
                 Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
                 == PackageManager.PERMISSION_GRANTED;
     }
 
+    private DevicePolicyManager retrieveDevicePolicyManagerFromUserContext(UserHandle userHandle) {
+        Context userContext;
+        long ident = Binder.clearCallingIdentity();
+        try {
+            userContext = mContext.createPackageContextAsUser(
+                    mContext.getPackageName(), /* flags= */ 0, userHandle);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Unknown package name");
+            return null;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        return userContext.getSystemService(DevicePolicyManager.class);
+    }
+
+    private boolean callerCanManageDevicePolicyManagedSubscriptions(String callingPackage) {
+        // isProfileOwner/isDeviceOwner needs to callers user, so create device policy manager
+        // with the correct context associated with the caller.
+        DevicePolicyManager devicePolicyManager =
+                retrieveDevicePolicyManagerFromUserContext(Binder.getCallingUserHandle());
+        if (devicePolicyManager == null) {
+            Log.w(TAG, "Unable to get device policy manager");
+            return false;
+        }
+        boolean isAdmin =
+                devicePolicyManager.isProfileOwnerApp(callingPackage)
+                        || devicePolicyManager.isDeviceOwnerApp(callingPackage);
+        return isAdmin || mContext.checkCallingOrSelfPermission(
+                Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
     @Override
     public boolean isSimPortAvailable(int cardId, int portIndex, String callingPackage) {
         mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
@@ -2073,6 +2272,34 @@
         return changeEnabled;
     }
 
+
+    @Override
+    public void setPsimConversionSupportedCarriers(int[] carrierIds) {
+        if (!callerCanWriteEmbeddedSubscriptions()) {
+            throw new SecurityException(
+                    "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to "
+                            + "set pSIM conversion supported carriers");
+        }
+        mPsimConversionSupportedCarrierIds = Arrays.stream(carrierIds).boxed()
+                .collect(Collectors.toList());
+    }
+
+
+
+    @Override
+    public boolean isPsimConversionSupported(int carrierId) {
+        if (!callerCanWriteEmbeddedSubscriptions()) {
+            throw new SecurityException(
+                    "Must have WRITE_EMBEDDED_SUBSCRIPTIONS "
+                            + "to check if the carrier is supported pSIM conversion");
+        }
+        if (mPsimConversionSupportedCarrierIds == null
+                || mPsimConversionSupportedCarrierIds.isEmpty()) {
+            return false;
+        }
+        return mPsimConversionSupportedCarrierIds.contains(carrierId);
+    }
+
     /**
      * Make sure the device has required telephony feature
      *
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java b/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java
index 6554077..234723f 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java
@@ -263,9 +263,10 @@
             PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId(),
                     KEY_NR_SA_DISABLE_POLICY_INT, KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY);
             mNrSaDisablePolicy = bundle.getInt(KEY_NR_SA_DISABLE_POLICY_INT);
-            mIsNrSaSupported = Arrays.stream(
-                    bundle.getIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY)).anyMatch(
-                        value -> value == CARRIER_NR_AVAILABILITY_SA);
+            int[] nrAvailabilities = bundle.getIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY);
+            mIsNrSaSupported = nrAvailabilities != null
+                    && Arrays.stream(nrAvailabilities).anyMatch(
+                            value -> value == CARRIER_NR_AVAILABILITY_SA);
 
             Log.d(TAG, "setNrSaDisablePolicy : NrSaDisablePolicy = "
                     + mNrSaDisablePolicy + ", IsNrSaSupported = "  + mIsNrSaSupported);
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index 9f3ec3b..f4f632e 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -21,10 +21,10 @@
 import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED;
 import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_REGISTERED;
 import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_NONE;
+import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS;
 import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK;
 import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT;
 import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_RAT_BLOCK;
-import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS;
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
 
 import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAIC;
@@ -49,7 +49,6 @@
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_NONE;
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE;
 
-import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.app.Activity;
 import android.app.Notification;
@@ -124,7 +123,6 @@
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.emergency.EmergencyStateTracker;
 import com.android.internal.telephony.flags.FeatureFlags;
-import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.metrics.ImsStats;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
@@ -2561,7 +2559,6 @@
 
     /** Clear the IMS phone number from IMS associated Uris when IMS registration is lost. */
     @VisibleForTesting
-    @FlaggedApi(Flags.FLAG_CLEAR_CACHED_IMS_PHONE_NUMBER_WHEN_DEVICE_LOST_IMS_REGISTRATION)
     public void clearPhoneNumberForSourceIms() {
         int subId = getSubId();
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
@@ -2832,6 +2829,15 @@
         mCT.triggerNotifyAnbr(mediaType, direction, bitsPerSecond);
     }
 
+    /**
+     * Check whether making a call using Wi-Fi is possible or not.
+     * @return {code true} if IMS is registered over IWLAN else return {code false}.
+     */
+    public boolean canMakeWifiCall() {
+        return isImsRegistered() && (getImsRegistrationTech()
+                == ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
+    }
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
         IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index e95433c..b5a052d 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -1259,8 +1259,6 @@
         }
     }
 
-    private @NonNull final FeatureFlags mFeatureFlags;
-
     //***** Events
 
 
@@ -1273,8 +1271,9 @@
     @VisibleForTesting
     public ImsPhoneCallTracker(ImsPhone phone, ConnectorFactory factory, Executor executor,
             FeatureFlags featureFlags) {
+        super(featureFlags);
+
         this.mPhone = phone;
-        mFeatureFlags = featureFlags;
         mConnectorFactory = factory;
         if (executor != null) {
             mExecutor = executor;
@@ -4453,13 +4452,15 @@
         public void onCallQualityChanged(ImsCall imsCall, CallQuality callQuality) {
             // convert ServiceState.radioTech to TelephonyManager.NetworkType constant
             mPhone.onCallQualityChanged(callQuality, imsCall.getNetworkType());
-            String callId = imsCall.getSession().getCallId();
-            CallQualityMetrics cqm = mCallQualityMetrics.get(callId);
-            if (cqm == null) {
-                cqm = new CallQualityMetrics(mPhone);
+            if (imsCall.getSession() != null) {
+                String callId = imsCall.getSession().getCallId();
+                CallQualityMetrics cqm = mCallQualityMetrics.get(callId);
+                if (cqm == null) {
+                    cqm = new CallQualityMetrics(mPhone);
+                }
+                cqm.saveCallQuality(callQuality);
+                mCallQualityMetrics.put(callId, cqm);
             }
-            cqm.saveCallQuality(callQuality);
-            mCallQualityMetrics.put(callId, cqm);
 
             ImsPhoneConnection conn = findConnection(imsCall);
             if (conn != null) {
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
index 104c925..a71355d 100755
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
@@ -1357,17 +1357,15 @@
      * @param imsCall The call to check for changes in extras.
      * @return Whether the extras fields have been changed.
      */
-     boolean updateExtras(ImsCall imsCall) {
+    boolean updateExtras(ImsCall imsCall) {
         if (imsCall == null) {
             return false;
         }
-
         final ImsCallProfile callProfile = imsCall.getCallProfile();
         final Bundle extras = callProfile != null ? callProfile.mCallExtras : null;
         if (extras == null && DBG) {
             Rlog.d(LOG_TAG, "Call profile extras are null.");
         }
-
         final boolean changed = !areBundlesEqual(extras, mExtras);
         if (changed) {
             updateImsCallRatFromExtras(extras);
@@ -1377,11 +1375,44 @@
             if (extras != null) {
                 mExtras.putAll(extras);
             }
+            if (com.android.server.telecom.flags.Flags.businessCallComposer()) {
+                maybeInjectBusinessComposerExtras(mExtras);
+            }
             setConnectionExtras(mExtras);
         }
         return changed;
     }
 
+    /**
+     * The Ims Vendor is responsible for setting the ImsCallProfile business call composer
+     * values (ImsCallProfile.EXTRA_IS_BUSINESS_CALL and
+     * ImsCallProfile.EXTRA_ASSERTED_DISPLAY_NAME). This helper notifies Telecom of the business
+     * composer values which will then be injected into the android.telecom.Call object.
+     */
+    private void maybeInjectBusinessComposerExtras(Bundle extras) {
+        if (extras == null) {
+            return;
+        }
+        try {
+            if (extras.containsKey(ImsCallProfile.EXTRA_IS_BUSINESS_CALL)
+                    && !extras.containsKey(android.telecom.Call.EXTRA_IS_BUSINESS_CALL)) {
+                boolean v = extras.getBoolean(ImsCallProfile.EXTRA_IS_BUSINESS_CALL);
+                Rlog.i(LOG_TAG, String.format("mIBCE: EXTRA_IS_BUSINESS_CALL=[%s]", v));
+                extras.putBoolean(android.telecom.Call.EXTRA_IS_BUSINESS_CALL, v);
+            }
+
+            if (extras.containsKey(ImsCallProfile.EXTRA_ASSERTED_DISPLAY_NAME)
+                    && !extras.containsKey(android.telecom.Call.EXTRA_ASSERTED_DISPLAY_NAME)) {
+                String v = extras.getString(ImsCallProfile.EXTRA_ASSERTED_DISPLAY_NAME);
+                Rlog.i(LOG_TAG, String.format("mIBCE: ASSERTED_DISPLAY_NAME=[%s]", v));
+                extras.putString(android.telecom.Call.EXTRA_ASSERTED_DISPLAY_NAME, v);
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
     private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
         if (extras == null || newExtras == null) {
             return extras == newExtras;
diff --git a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
index cc80a2e..a315f1e 100644
--- a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
+++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
@@ -1125,7 +1125,7 @@
                 roundAndConvertMillisToSeconds(stats.registeringMillis),
                 roundAndConvertMillisToSeconds(stats.unregisteredMillis),
                 stats.isIwlanCrossSim,
-                roundAndConvertMillisToSeconds(stats.registeredTimes));
+                stats.registeredTimes);
     }
 
     private static StatsEvent buildStatsEvent(ImsRegistrationTermination termination) {
diff --git a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
index 4a1fdb9..f3fe8fa 100644
--- a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
+++ b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
@@ -2291,8 +2291,6 @@
                     normalizeDurationTo24H(stats[i].registeringMillis, intervalMillis);
             stats[i].unregisteredMillis =
                     normalizeDurationTo24H(stats[i].unregisteredMillis, intervalMillis);
-            stats[i].registeredTimes =
-                    normalizeDurationTo24H(stats[i].registeredTimes, intervalMillis);
         }
         return stats;
     }
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramController.java b/src/java/com/android/internal/telephony/satellite/DatagramController.java
index 0c68d4b..877eaf1 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramController.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramController.java
@@ -18,6 +18,8 @@
 
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_IDLE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_OFF;
 
 import android.annotation.NonNull;
 import android.content.Context;
@@ -29,6 +31,7 @@
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -50,8 +53,12 @@
     public static final long MAX_DATAGRAM_ID = (long) Math.pow(2, 16);
     public static final int ROUNDING_UNIT = 10;
     public static final long SATELLITE_ALIGN_TIMEOUT = TimeUnit.SECONDS.toMillis(30);
-    public static final long DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT =
-            TimeUnit.SECONDS.toMillis(60);
+    /** This type is used by CTS to override the satellite align timeout */
+    public static final int TIMEOUT_TYPE_ALIGN = 1;
+    /** This type is used by CTS to override the time to wait for connected state */
+    public static final int TIMEOUT_TYPE_DATAGRAM_WAIT_FOR_CONNECTED_STATE = 2;
+    /** This type is used by CTS to override the time to wait for response of the send request */
+    public static final int TIMEOUT_TYPE_WAIT_FOR_DATAGRAM_SENDING_RESPONSE = 3;
     private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
     private static final boolean DEBUG = !"user".equals(Build.TYPE);
 
@@ -80,7 +87,8 @@
     private SatelliteDatagram mDemoModeDatagram;
     private boolean mIsDemoMode = false;
     private long mAlignTimeoutDuration = SATELLITE_ALIGN_TIMEOUT;
-    private long mDatagramWaitTimeForConnectedState = DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT;
+    private long mDatagramWaitTimeForConnectedState;
+    private long mModemImageSwitchingDuration;
     @GuardedBy("mLock")
     @SatelliteManager.SatelliteModemState
     private int mSatelltieModemState = SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN;
@@ -132,6 +140,9 @@
         // Create the DatagramReceiver singleton,
         // which is used to receive satellite datagrams.
         mDatagramReceiver = DatagramReceiver.make(mContext, looper, this);
+
+        mDatagramWaitTimeForConnectedState = getDatagramWaitForConnectedStateTimeoutMillis();
+        mModemImageSwitchingDuration = getSatelliteModemImageSwitchingDurationMillis();
     }
 
     /**
@@ -367,25 +378,51 @@
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public long getDatagramWaitTimeForConnectedState() {
-        return mDatagramWaitTimeForConnectedState;
+        synchronized (mLock) {
+            if (mSatelltieModemState == SATELLITE_MODEM_STATE_OFF
+                    || mSatelltieModemState == SATELLITE_MODEM_STATE_IDLE) {
+                return (mDatagramWaitTimeForConnectedState + mModemImageSwitchingDuration);
+            }
+            return mDatagramWaitTimeForConnectedState;
+        }
     }
 
     /**
-     * This API can be used by only CTS to update the timeout duration in milliseconds whether
-     * the device is aligned with the satellite for demo mode
+     * This API can be used by only CTS to timeout durations used by DatagramController module.
      *
      * @param timeoutMillis The timeout duration in millisecond.
      * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
      */
-    boolean setSatelliteDeviceAlignedTimeoutDuration(long timeoutMillis) {
+    boolean setDatagramControllerTimeoutDuration(
+            boolean reset, int timeoutType, long timeoutMillis) {
         if (!isMockModemAllowed()) {
-            loge("Updating align timeout duration is not allowed");
+            loge("Updating timeout duration is not allowed");
             return false;
         }
 
-        logd("setSatelliteDeviceAlignedTimeoutDuration: timeoutMillis=" + timeoutMillis);
-        mAlignTimeoutDuration = timeoutMillis;
-        mDatagramWaitTimeForConnectedState = timeoutMillis;
+        logd("setDatagramControllerTimeoutDuration: timeoutMillis=" + timeoutMillis
+                + ", reset=" + reset + ", timeoutType=" + timeoutType);
+        if (timeoutType == TIMEOUT_TYPE_ALIGN) {
+            if (reset) {
+                mAlignTimeoutDuration = SATELLITE_ALIGN_TIMEOUT;
+            } else {
+                mAlignTimeoutDuration = timeoutMillis;
+            }
+        } else if (timeoutType == TIMEOUT_TYPE_DATAGRAM_WAIT_FOR_CONNECTED_STATE) {
+            if (reset) {
+                mDatagramWaitTimeForConnectedState =
+                        getDatagramWaitForConnectedStateTimeoutMillis();
+                mModemImageSwitchingDuration = getSatelliteModemImageSwitchingDurationMillis();
+            } else {
+                mDatagramWaitTimeForConnectedState = timeoutMillis;
+                mModemImageSwitchingDuration = 0;
+            }
+        } else if (timeoutType == TIMEOUT_TYPE_WAIT_FOR_DATAGRAM_SENDING_RESPONSE) {
+            mDatagramDispatcher.setWaitTimeForDatagramSendingResponse(reset, timeoutMillis);
+        } else {
+            loge("Invalid timeout type " + timeoutType);
+            return false;
+        }
         return true;
     }
 
@@ -404,6 +441,16 @@
         }
     }
 
+    private long getDatagramWaitForConnectedStateTimeoutMillis() {
+        return mContext.getResources().getInteger(
+                R.integer.config_datagram_wait_for_connected_state_timeout_millis);
+    }
+
+    private long getSatelliteModemImageSwitchingDurationMillis() {
+        return mContext.getResources().getInteger(
+                R.integer.config_satellite_modem_image_switching_duration_millis);
+    }
+
     /**
      * This API can be used by only CTS to override the cached value for the device overlay config
      * value : config_send_satellite_datagram_to_modem_in_demo_mode, which determines whether
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
index ae4c1f2..5cac1dd 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.satellite;
 
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
 import static com.android.internal.telephony.satellite.DatagramController.ROUNDING_UNIT;
@@ -58,6 +59,8 @@
     private static final int EVENT_SEND_SATELLITE_DATAGRAM_DONE = 2;
     private static final int EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT = 3;
     private static final int EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT = 4;
+    private static final int EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT = 5;
+    private static final int EVENT_ABORT_SENDING_SATELLITE_DATAGRAMS_DONE = 6;
 
     @NonNull private static DatagramDispatcher sInstance;
     @NonNull private final Context mContext;
@@ -93,6 +96,8 @@
     private final LinkedHashMap<Long, SendSatelliteDatagramArgument>
             mPendingNonEmergencyDatagramsMap = new LinkedHashMap<>();
 
+    private long mWaitTimeForDatagramSendingResponse;
+
     /**
      * Create the DatagramDispatcher singleton instance.
      * @param context The Context to use to create the DatagramDispatcher.
@@ -126,6 +131,7 @@
         synchronized (mLock) {
             mSendingDatagramInProgress = false;
         }
+        mWaitTimeForDatagramSendingResponse = getWaitForDatagramSendingResponseTimeoutMillis();
     }
 
     private static final class DatagramDispatcherHandlerRequest {
@@ -196,13 +202,17 @@
                         (SendSatelliteDatagramArgument) request.argument;
                 onCompleted = obtainMessage(EVENT_SEND_SATELLITE_DATAGRAM_DONE, request);
 
-                if (mIsDemoMode && !shouldSendDatagramToModemInDemoMode()) {
-                    AsyncResult.forMessage(onCompleted, SATELLITE_RESULT_SUCCESS, null);
-                    onCompleted.sendToTarget();
-                } else {
-                    SatelliteModemInterface.getInstance().sendSatelliteDatagram(argument.datagram,
-                            argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
-                            argument.needFullScreenPointingUI, onCompleted);
+                synchronized (mLock) {
+                    if (mIsDemoMode && !shouldSendDatagramToModemInDemoMode()) {
+                        AsyncResult.forMessage(onCompleted, SATELLITE_RESULT_SUCCESS, null);
+                        onCompleted.sendToTarget();
+                    } else {
+                        SatelliteModemInterface.getInstance().sendSatelliteDatagram(
+                                argument.datagram,
+                                argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+                                argument.needFullScreenPointingUI, onCompleted);
+                        startWaitForDatagramSendingResponseTimer(argument);
+                    }
                 }
                 break;
             }
@@ -223,13 +233,25 @@
                             break;
                         }
                     }
-
                     logd("EVENT_SEND_SATELLITE_DATAGRAM_DONE error: " + error);
-                    // log metrics about the outgoing datagram
-                    reportSendDatagramCompleted(argument, error);
 
+                    /*
+                     * The response should be ignored if either of the following hold
+                     * 1) Framework has already received this response from the vendor service.
+                     * 2) Framework has timed out to wait for the response from vendor service for
+                     *    the send request.
+                     * 3) All pending send requests have been aborted due to some error.
+                     */
+                    if (!shouldProcessEventSendSatelliteDatagramDone(argument)) {
+                        logw("The message " + argument.datagramId + " was already processed");
+                        break;
+                    }
+
+                    stopWaitForDatagramSendingResponseTimer();
                     mSendingDatagramInProgress = false;
 
+                    // Log metrics about the outgoing datagram
+                    reportSendDatagramCompleted(argument, error);
                     // Remove current datagram from pending map.
                     if (argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
                         mPendingEmergencyDatagramsMap.remove(argument.datagramId);
@@ -278,6 +300,11 @@
                 break;
             }
 
+            case EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT:
+                handleEventWaitForDatagramSendingResponseTimedOut(
+                        (SendSatelliteDatagramArgument) msg.obj);
+                break;
+
             case EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT: {
                 handleEventSatelliteAlignedTimeout((DatagramDispatcherHandlerRequest) msg.obj);
                 break;
@@ -596,6 +623,7 @@
 
         stopSatelliteAlignedTimer();
         stopDatagramWaitForConnectedStateTimer();
+        stopWaitForDatagramSendingResponseTimer();
         mIsDemoMode = false;
         mSendSatelliteDatagramRequest = null;
         mIsAligned = false;
@@ -620,6 +648,32 @@
         return hasMessages(EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT);
     }
 
+    /**
+     * This API is used by CTS tests to override the mWaitTimeForDatagramSendingResponse.
+     */
+    void setWaitTimeForDatagramSendingResponse(boolean reset, long timeoutMillis) {
+        if (reset) {
+            mWaitTimeForDatagramSendingResponse = getWaitForDatagramSendingResponseTimeoutMillis();
+        } else {
+            mWaitTimeForDatagramSendingResponse = timeoutMillis;
+        }
+    }
+
+    private void startWaitForDatagramSendingResponseTimer(
+            @NonNull SendSatelliteDatagramArgument argument) {
+        if (hasMessages(EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT)) {
+            logd("WaitForDatagramSendingResponseTimer was already started");
+            return;
+        }
+        sendMessageDelayed(obtainMessage(
+                EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT, argument),
+                mWaitTimeForDatagramSendingResponse);
+    }
+
+    private void stopWaitForDatagramSendingResponseTimer() {
+        removeMessages(EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT);
+    }
+
     private void handleEventDatagramWaitForConnectedStateTimedOut() {
         logw("Timed out to wait for satellite connected before sending datagrams");
         synchronized (mLock) {
@@ -654,6 +708,61 @@
         }
     }
 
+    private long getWaitForDatagramSendingResponseTimeoutMillis() {
+        return mContext.getResources().getInteger(
+                R.integer.config_wait_for_datagram_sending_response_timeout_millis);
+    }
+
+    private boolean shouldProcessEventSendSatelliteDatagramDone(
+            @NonNull SendSatelliteDatagramArgument argument) {
+        synchronized (mLock) {
+            if (argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
+                return mPendingEmergencyDatagramsMap.containsKey(argument.datagramId);
+            } else {
+                return mPendingNonEmergencyDatagramsMap.containsKey(argument.datagramId);
+            }
+        }
+    }
+
+    private void handleEventWaitForDatagramSendingResponseTimedOut(
+            @NonNull SendSatelliteDatagramArgument argument) {
+        synchronized (mLock) {
+            logw("Timed out to wait for the response of the request to send the datagram "
+                    + argument.datagramId);
+
+            // Ask vendor service to abort all datagram-sending requests
+            SatelliteModemInterface.getInstance().abortSendingSatelliteDatagrams(
+                    obtainMessage(EVENT_ABORT_SENDING_SATELLITE_DATAGRAMS_DONE, argument));
+            mSendingDatagramInProgress = false;
+
+            // Update send status
+            mDatagramController.updateSendStatus(argument.subId,
+                    SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
+                    getPendingDatagramCount(), SATELLITE_RESULT_MODEM_TIMEOUT);
+            mDatagramController.updateSendStatus(argument.subId,
+                    SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                    0, SatelliteManager.SATELLITE_RESULT_SUCCESS);
+
+            // Send response for current datagram after updating datagram transfer state
+            // internally.
+            argument.callback.accept(SATELLITE_RESULT_MODEM_TIMEOUT);
+
+            // Log metrics about the outgoing datagram
+            reportSendDatagramCompleted(argument, SATELLITE_RESULT_MODEM_TIMEOUT);
+            mControllerMetricsStats.reportOutgoingDatagramFailCount(argument.datagramType);
+            // Remove current datagram from pending map.
+            if (argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
+                mPendingEmergencyDatagramsMap.remove(argument.datagramId);
+            } else {
+                mPendingNonEmergencyDatagramsMap.remove(argument.datagramId);
+            }
+
+            // Abort sending all the pending datagrams
+            abortSendingPendingDatagrams(argument.subId,
+                    SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
+        }
+    }
+
     /**
      * This API can be used by only CTS to override the cached value for the device overlay config
      * value : config_send_satellite_datagram_to_modem_in_demo_mode, which determines whether
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index aaabaff..0d3973d 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -16,10 +16,13 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.provider.Settings.ACTION_SATELLITE_SETTING;
 import static android.telephony.CarrierConfigManager.KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE;
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL;
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT;
+import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL;
 import static android.telephony.SubscriptionManager.SATELLITE_ATTACH_ENABLED_FOR_CARRIER;
+import static android.telephony.SubscriptionManager.SATELLITE_ENTITLEMENT_STATUS;
 import static android.telephony.SubscriptionManager.isValidSubscriptionId;
 import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE;
 import static android.telephony.satellite.SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS;
@@ -27,12 +30,17 @@
 import static android.telephony.satellite.SatelliteManager.KEY_NTN_SIGNAL_STRENGTH;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
 import android.annotation.ArrayRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -42,6 +50,7 @@
 import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.net.Uri;
 import android.net.wifi.WifiManager;
 import android.nfc.NfcAdapter;
 import android.os.AsyncResult;
@@ -62,6 +71,7 @@
 import android.os.ServiceSpecificException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
@@ -105,9 +115,11 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
 
 /**
@@ -126,6 +138,11 @@
     public static final int SATELLITE_MODE_ENABLED_TRUE = 1;
     public static final int SATELLITE_MODE_ENABLED_FALSE = 0;
     public static final int INVALID_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE = -1;
+    /**
+     * This is used by CTS to override the timeout duration to wait for the response of the request
+     * to enable satellite.
+     */
+    public static final int TIMEOUT_TYPE_WAIT_FOR_SATELLITE_ENABLING_RESPONSE = 1;
 
     /** Key used to read/write OEM-enabled satellite provision status in shared preferences. */
     private static final String OEM_ENABLED_SATELLITE_PROVISION_STATUS_KEY =
@@ -169,6 +186,7 @@
     private static final int EVENT_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING_DONE = 36;
     private static final int EVENT_SERVICE_STATE_CHANGED = 37;
     private static final int EVENT_SATELLITE_CAPABILITIES_CHANGED = 38;
+    private static final int EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT = 39;
 
     @NonNull private static SatelliteController sInstance;
     @NonNull private final Context mContext;
@@ -334,6 +352,18 @@
      * carrierPlmnList. */
     @GuardedBy("mSupportedSatelliteServicesLock")
     private final SparseArray<List<String>> mMergedPlmnListPerCarrier = new SparseArray<>();
+    private static AtomicLong sNextSatelliteEnableRequestId = new AtomicLong(0);
+    private long mWaitTimeForSatelliteEnablingResponse;
+
+    /** Key used to read/write satellite system notification done in shared preferences. */
+    private static final String SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY =
+            "satellite_system_notification_done_key";
+    // The notification tag used when showing a notification. The combination of notification tag
+    // and notification id should be unique within the phone app.
+    private static final String NOTIFICATION_TAG = "SatelliteController";
+    private static final int NOTIFICATION_ID = 1;
+    private static final String NOTIFICATION_CHANNEL = "satelliteChannel";
+    private static final String NOTIFICATION_CHANNEL_ID = "satellite";
 
     /**
      * @return The singleton instance of SatelliteController.
@@ -427,6 +457,7 @@
         mDSM.registerForSignalStrengthReportDecision(this, CMD_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING,
                 null);
         loadSatelliteSharedPreferences();
+        mWaitTimeForSatelliteEnablingResponse = getWaitForSatelliteEnablingResponseTimeoutMillis();
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@@ -634,12 +665,15 @@
         public boolean enableSatellite;
         public boolean enableDemoMode;
         @NonNull public Consumer<Integer> callback;
+        public long requestId;
 
         RequestSatelliteEnabledArgument(boolean enableSatellite, boolean enableDemoMode,
                 Consumer<Integer> callback) {
             this.enableSatellite = enableSatellite;
             this.enableDemoMode = enableDemoMode;
             this.callback = callback;
+            this.requestId = sNextSatelliteEnableRequestId.getAndUpdate(
+                    n -> ((n + 1) % Long.MAX_VALUE));
         }
     }
 
@@ -792,6 +826,17 @@
                 int error =  SatelliteServiceUtils.getSatelliteError(ar, "setSatelliteEnabled");
                 logd("EVENT_SET_SATELLITE_ENABLED_DONE = " + error);
 
+                /*
+                 * The timer to wait for EVENT_SET_SATELLITE_ENABLED_DONE might have expired and
+                 * thus the request resources might have been cleaned up.
+                 */
+                if (!shouldProcessEventSetSatelliteEnabledDone(argument)) {
+                    logw("The request ID=" + argument.requestId + ", enableSatellite="
+                            + argument.enableSatellite + " was already processed");
+                    return;
+                }
+                stopWaitForSatelliteEnablingResponseTimer(argument);
+
                 if (error == SATELLITE_RESULT_SUCCESS) {
                     if (argument.enableSatellite) {
                         synchronized (mSatelliteEnabledRequestLock) {
@@ -867,6 +912,11 @@
                 break;
             }
 
+            case EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT:
+                handleEventWaitForSatelliteEnablingResponseTimedOut(
+                        (RequestSatelliteEnabledArgument) msg.obj);
+                break;
+
             case CMD_IS_SATELLITE_ENABLED: {
                 request = (SatelliteControllerHandlerRequest) msg.obj;
                 onCompleted = obtainMessage(EVENT_IS_SATELLITE_ENABLED_DONE, request);
@@ -2143,18 +2193,56 @@
     }
 
     /**
-     * This API can be used by only CTS to update the timeout duration in milliseconds whether
-     * the device is aligned with the satellite for demo mode
+     * This API can be used by only CTS to override timeout durations used by DatagramController
+     * module.
      *
      * @param timeoutMillis The timeout duration in millisecond.
      * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
      */
-    public boolean setSatelliteDeviceAlignedTimeoutDuration(long timeoutMillis) {
+    public boolean setDatagramControllerTimeoutDuration(
+            boolean reset, int timeoutType, long timeoutMillis) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("setSatelliteDeviceAlignedTimeoutDuration: oemEnabledSatelliteFlag is disabled");
+            logd("setDatagramControllerTimeoutDuration: oemEnabledSatelliteFlag is disabled");
             return false;
         }
-        return mDatagramController.setSatelliteDeviceAlignedTimeoutDuration(timeoutMillis);
+        logd("setDatagramControllerTimeoutDuration: reset=" + reset + ", timeoutType="
+                + timeoutType + ", timeoutMillis=" + timeoutMillis);
+        return mDatagramController.setDatagramControllerTimeoutDuration(
+                reset, timeoutType, timeoutMillis);
+    }
+
+    /**
+     * This API can be used by only CTS to override timeout durations used by SatelliteController
+     * module.
+     *
+     * @param timeoutMillis The timeout duration in millisecond.
+     * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
+     */
+    public boolean setSatelliteControllerTimeoutDuration(
+            boolean reset, int timeoutType, long timeoutMillis) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("setSatelliteControllerTimeoutDuration: oemEnabledSatelliteFlag is disabled");
+            return false;
+        }
+        if (!isMockModemAllowed()) {
+            logd("setSatelliteControllerTimeoutDuration: mock modem is not allowed");
+            return false;
+        }
+        logd("setSatelliteControllerTimeoutDuration: reset=" + reset + ", timeoutType="
+                + timeoutType + ", timeoutMillis=" + timeoutMillis);
+        if (timeoutType == TIMEOUT_TYPE_WAIT_FOR_SATELLITE_ENABLING_RESPONSE) {
+            if (reset) {
+                mWaitTimeForSatelliteEnablingResponse =
+                        getWaitForSatelliteEnablingResponseTimeoutMillis();
+            } else {
+                mWaitTimeForSatelliteEnablingResponse = timeoutMillis;
+            }
+            logd("mWaitTimeForSatelliteEnablingResponse=" + mWaitTimeForSatelliteEnablingResponse);
+        } else {
+            logw("Invalid timeoutType=" + timeoutType);
+            return false;
+        }
+        return true;
     }
 
     /**
@@ -2513,19 +2601,28 @@
                 }
             };
         }
+        logd("onSatelliteEntitlementStatusUpdated subId=" + subId + " , entitlementEnabled="
+                + entitlementEnabled + ", allowedPlmnList=" + (Objects.equals(null, allowedPlmnList)
+                ? "" : allowedPlmnList + ""));
 
         synchronized (mSupportedSatelliteServicesLock) {
             if (mSatelliteEntitlementStatusPerCarrier.get(subId, false) != entitlementEnabled) {
                 logd("update the carrier satellite enabled to " + entitlementEnabled);
                 mSatelliteEntitlementStatusPerCarrier.put(subId, entitlementEnabled);
+                try {
+                    mSubscriptionManagerService.setSubscriptionProperty(subId,
+                            SATELLITE_ENTITLEMENT_STATUS, entitlementEnabled ? "1" : "0");
+                } catch (IllegalArgumentException | SecurityException e) {
+                    loge("onSatelliteEntitlementStatusUpdated: setSubscriptionProperty, e=" + e);
+                }
             }
             mMergedPlmnListPerCarrier.remove(subId);
 
             mEntitlementPlmnListPerCarrier.put(subId, allowedPlmnList);
             updatePlmnListPerCarrier(subId);
             configureSatellitePlmnForCarrier(subId);
+            mSubscriptionManagerService.setSatelliteEntitlementPlmnList(subId, allowedPlmnList);
 
-            // TODO b/322143408 store entitlement status in telephony db.
             if (mSatelliteEntitlementStatusPerCarrier.get(subId, false)) {
                 removeAttachRestrictionForCarrier(subId,
                         SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT, callback);
@@ -2726,6 +2823,7 @@
         Message onCompleted = obtainMessage(EVENT_SET_SATELLITE_ENABLED_DONE, request);
         mSatelliteModemInterface.requestSatelliteEnabled(argument.enableSatellite,
                 argument.enableDemoMode, onCompleted);
+        startWaitForSatelliteEnablingResponseTimer(argument);
     }
 
     private void handleRequestSatelliteAttachRestrictionForCarrierCmd(
@@ -3144,7 +3242,8 @@
         PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId,
                 KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
                 KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
-                KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT);
+                KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT,
+                KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL);
         if (config == null || config.isEmpty()) {
             config = CarrierConfigManager.getDefaultConfig();
         }
@@ -3161,8 +3260,7 @@
         }
 
         updateCarrierConfig(subId);
-        // TODO b/322143408 read the telephony db to get the entitlementStatus and update the
-        //  restriction
+        updateEntitlementPlmnListPerCarrier(subId);
         updateSupportedSatelliteServicesForActiveSubscriptions();
         configureSatellitePlmnForCarrier(subId);
 
@@ -3172,6 +3270,7 @@
         }
 
         setSatelliteAttachEnabledForCarrierOnSimLoaded(subId);
+        updateRestrictReasonForEntitlementPerCarrier(subId);
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@@ -3181,6 +3280,31 @@
         }
     }
 
+    /** If there is no cached entitlement plmn list, read it from the db and use it if it is not an
+     * empty list. */
+    private void updateEntitlementPlmnListPerCarrier(int subId) {
+        if (!getConfigForSubId(subId).getBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false)) {
+            logd("don't support entitlement");
+            return;
+        }
+
+        synchronized (mSupportedSatelliteServicesLock) {
+            if (mEntitlementPlmnListPerCarrier.indexOfKey(subId) < 0) {
+                logd("updateEntitlementPlmnListPerCarrier: no correspondent cache, load from "
+                        + "persist storage");
+                List<String> entitlementPlmnList =
+                        mSubscriptionManagerService.getSatelliteEntitlementPlmnList(subId);
+                if (entitlementPlmnList.isEmpty()) {
+                    loge("updateEntitlementPlmnListPerCarrier: no data for subId(" + subId + ")");
+                    return;
+                }
+                logd("updateEntitlementPlmnListPerCarrier: entitlementPlmnList="
+                        + entitlementPlmnList);
+                mEntitlementPlmnListPerCarrier.put(subId, entitlementPlmnList);
+            }
+        }
+    }
+
     /**
      * When a SIM is loaded, we need to check if users has enabled satellite attach for the carrier
      * associated with the SIM, and evaluate if satellite should be enabled for the carrier.
@@ -3285,6 +3409,54 @@
         }
     }
 
+    private void updateRestrictReasonForEntitlementPerCarrier(int subId) {
+        if (!getConfigForSubId(subId).getBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false)) {
+            logd("don't support entitlement");
+            return;
+        }
+
+        IIntegerConsumer callback = new IIntegerConsumer.Stub() {
+            @Override
+            public void accept(int result) {
+                logd("updateRestrictReasonForEntitlementPerCarrier:" + result);
+            }
+        };
+        synchronized (mSupportedSatelliteServicesLock) {
+            if (mSatelliteEntitlementStatusPerCarrier.indexOfKey(subId) < 0) {
+                logd("updateRestrictReasonForEntitlementPerCarrier: no correspondent cache, "
+                        + "load from persist storage");
+                String entitlementStatus = null;
+                try {
+                    entitlementStatus =
+                            mSubscriptionManagerService.getSubscriptionProperty(subId,
+                                    SATELLITE_ENTITLEMENT_STATUS, mContext.getOpPackageName(),
+                                    mContext.getAttributionTag());
+                } catch (IllegalArgumentException | SecurityException e) {
+                    loge("updateRestrictReasonForEntitlementPerCarrier, e=" + e);
+                }
+
+                if (entitlementStatus == null) {
+                    loge("updateRestrictReasonForEntitlementPerCarrier: invalid subId, subId="
+                            + subId + " set to default value");
+                    entitlementStatus = "0";
+                }
+
+                if (entitlementStatus.isEmpty()) {
+                    loge("updateRestrictReasonForEntitlementPerCarrier: no data for subId(" + subId
+                            + "). set to default value");
+                    entitlementStatus = "0";
+                }
+                boolean result = entitlementStatus.equals("1");
+                mSatelliteEntitlementStatusPerCarrier.put(subId, result);
+            }
+
+            if (!mSatelliteEntitlementStatusPerCarrier.get(subId, false)) {
+                addAttachRestrictionForCarrier(subId,
+                        SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT, callback);
+            }
+        }
+    }
+
     /**
      * Save user setting for enabling satellite attach for the carrier associated with the
      * {@code subId} to persistent storage.
@@ -3462,6 +3634,7 @@
 
     private void handleEventServiceStateChanged() {
         handleServiceStateForSatelliteConnectionViaCarrier();
+        determineSystemNotification();
     }
 
     private void handleServiceStateForSatelliteConnectionViaCarrier() {
@@ -3573,6 +3746,109 @@
         ((ResultReceiver) request.argument).send(error, bundle);
     }
 
+    private long getWaitForSatelliteEnablingResponseTimeoutMillis() {
+        return mContext.getResources().getInteger(
+                R.integer.config_wait_for_satellite_enabling_response_timeout_millis);
+    }
+
+    private void startWaitForSatelliteEnablingResponseTimer(
+            @NonNull RequestSatelliteEnabledArgument argument) {
+        synchronized (mSatelliteEnabledRequestLock) {
+            if (hasMessages(EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT, argument)) {
+                logd("WaitForSatelliteEnablingResponseTimer of request ID "
+                        + argument.requestId + " was already started");
+                return;
+            }
+            logd("Start timer to wait for response of the satellite enabling request ID="
+                    + argument.requestId + ", enableSatellite=" + argument.enableSatellite
+                    + ", mWaitTimeForSatelliteEnablingResponse="
+                    + mWaitTimeForSatelliteEnablingResponse);
+            sendMessageDelayed(obtainMessage(EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT,
+                            argument), mWaitTimeForSatelliteEnablingResponse);
+        }
+    }
+
+    private void stopWaitForSatelliteEnablingResponseTimer(
+            @NonNull RequestSatelliteEnabledArgument argument) {
+        synchronized (mSatelliteEnabledRequestLock) {
+            logd("Stop timer to wait for response of the satellite enabling request ID="
+                    + argument.requestId + ", enableSatellite=" + argument.enableSatellite);
+            removeMessages(EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT, argument);
+        }
+    }
+
+    private boolean shouldProcessEventSetSatelliteEnabledDone(
+            @NonNull RequestSatelliteEnabledArgument argument) {
+        synchronized (mSatelliteEnabledRequestLock) {
+            if (hasMessages(EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT, argument)) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private void handleEventWaitForSatelliteEnablingResponseTimedOut(
+            @NonNull RequestSatelliteEnabledArgument argument) {
+        logw("Timed out to wait for response of the satellite enabling request ID="
+                + argument.requestId + ", enableSatellite=" + argument.enableSatellite);
+
+        synchronized (mSatelliteEnabledRequestLock) {
+            if (mSatelliteEnabledRequest != null) {
+                if (mSatelliteEnabledRequest.enableSatellite && !argument.enableSatellite
+                        && mWaitingForRadioDisabled) {
+                    // Previous mSatelliteEnabledRequest is successful but waiting for
+                    // all radios to be turned off.
+                    mSatelliteEnabledRequest.callback.accept(SATELLITE_RESULT_SUCCESS);
+                    resetSatelliteEnabledRequest();
+                } else if (mSatelliteEnabledRequest.requestId == argument.requestId) {
+                    resetSatelliteEnabledRequest();
+                }
+            }
+        }
+        argument.callback.accept(SATELLITE_RESULT_MODEM_TIMEOUT);
+
+        synchronized (mIsSatelliteEnabledLock) {
+            if (argument.enableSatellite) {
+                if (!mWaitingForDisableSatelliteModemResponse && !mWaitingForSatelliteModemOff) {
+                    resetSatelliteEnabledRequest();
+                    IIntegerConsumer callback = new IIntegerConsumer.Stub() {
+                        @Override
+                        public void accept(int result) {
+                            logd("handleEventWaitForSatelliteEnablingResponseTimedOut: "
+                                    + "disable satellite result=" + result);
+                        }
+                    };
+                    Consumer<Integer> result =
+                            FunctionalUtils.ignoreRemoteException(callback::accept);
+                    RequestSatelliteEnabledArgument request = new RequestSatelliteEnabledArgument(
+                            false, false, result);
+                    synchronized (mSatelliteEnabledRequestLock) {
+                        mSatelliteEnabledRequest = request;
+                    }
+                    sendRequestAsync(CMD_SET_SATELLITE_ENABLED, request, null);
+                }
+                mControllerMetricsStats.reportServiceEnablementFailCount();
+                SessionMetricsStats.getInstance()
+                        .setInitializationResult(SATELLITE_RESULT_MODEM_TIMEOUT)
+                        .setRadioTechnology(getSupportedNtnRadioTechnology())
+                        .reportSessionMetrics();
+            } else {
+                /*
+                 * Unregister Importance Listener for Pointing UI when Satellite is disabled
+                 */
+                synchronized (mNeedsSatellitePointingLock) {
+                    if (mNeedsSatellitePointing) {
+                        mPointingAppController.removeListenerForPointingUI();
+                    }
+                }
+                moveSatelliteToOffStateAndCleanUpResources(SATELLITE_RESULT_MODEM_TIMEOUT, null);
+                mControllerMetricsStats.onSatelliteDisabled();
+                mWaitingForDisableSatelliteModemResponse = false;
+                mWaitingForSatelliteModemOff = false;
+            }
+        }
+    }
+
     /**
      * This API can be used by only CTS to override the cached value for the device overlay config
      * value : config_send_satellite_datagram_to_modem_in_demo_mode, which determines whether
@@ -3598,10 +3874,86 @@
         return true;
     }
 
+    private void determineSystemNotification() {
+        if (isUsingNonTerrestrialNetworkViaCarrier()) {
+            if (mSharedPreferences == null) {
+                try {
+                    mSharedPreferences = mContext.getSharedPreferences(SATELLITE_SHARED_PREF,
+                            Context.MODE_PRIVATE);
+                } catch (Exception e) {
+                    loge("Cannot get default shared preferences: " + e);
+                }
+            }
+            if (mSharedPreferences == null) {
+                loge("handleEventServiceStateChanged: Cannot get default shared preferences");
+                return;
+            }
+            if (!mSharedPreferences.getBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY, false)) {
+                showSatelliteSystemNotification();
+                mSharedPreferences.edit().putBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY,
+                        true).apply();
+            }
+        }
+    }
+
+    private void showSatelliteSystemNotification() {
+        logd("showSatelliteSystemNotification");
+        final NotificationChannel notificationChannel = new NotificationChannel(
+                NOTIFICATION_CHANNEL_ID,
+                NOTIFICATION_CHANNEL,
+                NotificationManager.IMPORTANCE_DEFAULT
+        );
+        notificationChannel.setSound(null, null);
+        NotificationManager notificationManager = mContext.getSystemService(
+                NotificationManager.class);
+        notificationManager.createNotificationChannel(notificationChannel);
+
+        Notification.Builder notificationBuilder = new Notification.Builder(mContext)
+                .setContentTitle(mContext.getResources().getString(
+                        R.string.satellite_notification_title))
+                .setContentText(mContext.getResources().getString(
+                        R.string.satellite_notification_summary))
+                .setSmallIcon(R.drawable.ic_satellite_alt_24px)
+                .setChannelId(NOTIFICATION_CHANNEL_ID)
+                .setAutoCancel(true)
+                .setColor(mContext.getColor(
+                        com.android.internal.R.color.system_notification_accent_color))
+                .setVisibility(Notification.VISIBILITY_PUBLIC);
+
+        // Add action to invoke `What to expect` dialog of Messaging application.
+        Intent intentOpenMessage = new Intent(Intent.ACTION_VIEW);
+        intentOpenMessage.setData(Uri.parse("sms:"));
+        // TODO : b/322733285 add putExtra to invoke "What to expect" dialog.
+        PendingIntent pendingIntentOpenMessage = PendingIntent.getActivity(mContext, 0,
+                intentOpenMessage, PendingIntent.FLAG_IMMUTABLE);
+
+        Notification.Action actionOpenMessage = new Notification.Action.Builder(0,
+                mContext.getResources().getString(R.string.satellite_notification_open_message),
+                pendingIntentOpenMessage).build();
+        notificationBuilder.addAction(actionOpenMessage);
+
+        // Add action to invoke Satellite setting activity in Settings.
+        Intent intentSatelliteSetting = new Intent(ACTION_SATELLITE_SETTING);
+        PendingIntent pendingIntentSatelliteSetting = PendingIntent.getActivity(mContext, 0,
+                intentSatelliteSetting, PendingIntent.FLAG_IMMUTABLE);
+
+        Notification.Action actionOpenSatelliteSetting = new Notification.Action.Builder(null,
+                mContext.getResources().getString(R.string.satellite_notification_how_it_works),
+                pendingIntentSatelliteSetting).build();
+        notificationBuilder.addAction(actionOpenSatelliteSetting);
+
+        notificationManager.notifyAsUser(NOTIFICATION_TAG, NOTIFICATION_ID,
+                notificationBuilder.build(), UserHandle.ALL);
+    }
+
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
 
+    private static void logw(@NonNull String log) {
+        Rlog.w(TAG, log);
+    }
+
     private static void loge(@NonNull String log) {
         Rlog.e(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 900e124..2f86eea 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
@@ -1317,6 +1317,35 @@
         }
     }
 
+    /**
+     * The satellite service should abort all datagram-sending requests.
+     *
+     * @param message The Message to send to result of the operation to.
+     */
+    public void abortSendingSatelliteDatagrams(@NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.abortSendingSatelliteDatagrams(new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        int error = SatelliteServiceUtils.fromSatelliteError(result);
+                        logd("abortSendingSatelliteDatagrams: " + error);
+                        Binder.withCleanCallingIdentity(() ->
+                                sendMessageWithResult(message, null, error));
+                    }
+                });
+            } catch (RemoteException e) {
+                loge("abortSendingSatelliteDatagrams: RemoteException " + e);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
+            }
+        } else {
+            loge("abortSendingSatelliteDatagrams: Satellite service is unavailable.");
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
+        }
+    }
+
     public boolean isSatelliteServiceSupported() {
         return mIsSatelliteServiceSupported;
     }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
index 541a029..5c79c28 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
@@ -358,6 +358,7 @@
         }
         return true;
     }
+
     /**
      * Adjusts listening timeout duration when demo mode is on
      *
@@ -711,6 +712,9 @@
                     && datagramTransferState.receiveState
                     == SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE) {
                 startNbIotInactivityTimer();
+            } else if (isSending(datagramTransferState.sendState)
+                    || isReceiving(datagramTransferState.receiveState)) {
+                restartNbIotInactivityTimer();
             }
         }
     }
@@ -761,9 +765,8 @@
 
         private void handleEventDatagramTransferStateChanged(
                 @NonNull DatagramTransferState datagramTransferState) {
-            if (datagramTransferState.sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING
-                    || datagramTransferState.receiveState
-                    == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING) {
+            if (isSending(datagramTransferState.sendState)
+                    || isReceiving(datagramTransferState.receiveState)) {
                 transitionTo(mTransferringState);
             }
         }
@@ -974,6 +977,11 @@
                 R.integer.config_satellite_nb_iot_inactivity_timeout_millis);
     }
 
+    private void restartNbIotInactivityTimer() {
+        stopNbIotInactivityTimer();
+        startNbIotInactivityTimer();
+    }
+
     private void startNbIotInactivityTimer() {
         if (isNbIotInactivityTimerStarted()) {
             logd("NB IOT inactivity timer is already started");
diff --git a/src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java b/src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java
index e7f66a3..4540b8a 100644
--- a/src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java
+++ b/src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony.security;
 
+import android.content.Context;
 import android.telephony.CellularIdentifierDisclosure;
 
 import com.android.internal.annotations.GuardedBy;
@@ -49,6 +50,7 @@
     private static CellularIdentifierDisclosureNotifier sInstance = null;
     private final long mWindowCloseDuration;
     private final TimeUnit mWindowCloseUnit;
+    private final CellularNetworkSecuritySafetySource mSafetySource;
     private final Object mEnabledLock = new Object();
 
     @GuardedBy("mEnabledLock")
@@ -61,11 +63,12 @@
     // outside of that thread would require additional synchronization.
     private Map<Integer, DisclosureWindow> mWindows;
 
-    public CellularIdentifierDisclosureNotifier() {
+    public CellularIdentifierDisclosureNotifier(CellularNetworkSecuritySafetySource safetySource) {
         this(
                 Executors.newSingleThreadScheduledExecutor(),
                 DEFAULT_WINDOW_CLOSE_DURATION_IN_MINUTES,
-                TimeUnit.MINUTES);
+                TimeUnit.MINUTES,
+                safetySource);
     }
 
     /**
@@ -79,18 +82,20 @@
     public CellularIdentifierDisclosureNotifier(
             ScheduledExecutorService notificationQueue,
             long windowCloseDuration,
-            TimeUnit windowCloseUnit) {
+            TimeUnit windowCloseUnit,
+            CellularNetworkSecuritySafetySource safetySource) {
         mSerializedWorkQueue = notificationQueue;
         mWindowCloseDuration = windowCloseDuration;
         mWindowCloseUnit = windowCloseUnit;
         mWindows = new HashMap<>();
+        mSafetySource = safetySource;
     }
 
     /**
      * Add a CellularIdentifierDisclosure to be tracked by this instance. If appropriate, this will
      * trigger a user notification.
      */
-    public void addDisclosure(int subId, CellularIdentifierDisclosure disclosure) {
+    public void addDisclosure(Context context, int subId, CellularIdentifierDisclosure disclosure) {
         Rlog.d(TAG, "Identifier disclosure reported: " + disclosure);
 
         synchronized (mEnabledLock) {
@@ -111,7 +116,7 @@
             // because we know that any actions taken on disabled will be scheduled after this
             // incrementAndNotify call.
             try {
-                mSerializedWorkQueue.execute(incrementAndNotify(subId));
+                mSerializedWorkQueue.execute(incrementAndNotify(context, subId));
             } catch (RejectedExecutionException e) {
                 Rlog.e(TAG, "Failed to schedule incrementAndNotify: " + e.getMessage());
             }
@@ -122,12 +127,12 @@
      * Re-enable if previously disabled. This means that {@code addDisclsoure} will start tracking
      * disclosures again and potentially emitting notifications.
      */
-    public void enable() {
+    public void enable(Context context) {
         synchronized (mEnabledLock) {
             Rlog.d(TAG, "enabled");
             mEnabled = true;
             try {
-                mSerializedWorkQueue.execute(onEnableNotifier());
+                mSerializedWorkQueue.execute(onEnableNotifier(context));
             } catch (RejectedExecutionException e) {
                 Rlog.e(TAG, "Failed to schedule onEnableNotifier: " + e.getMessage());
             }
@@ -139,12 +144,12 @@
      * This can be used to in response to a user disabling the feature to emit notifications.
      * If {@code addDisclosure} is called while in a disabled state, disclosures will be dropped.
      */
-    public void disable() {
+    public void disable(Context context) {
         Rlog.d(TAG, "disabled");
         synchronized (mEnabledLock) {
             mEnabled = false;
             try {
-                mSerializedWorkQueue.execute(onDisableNotifier());
+                mSerializedWorkQueue.execute(onDisableNotifier(context));
             } catch (RejectedExecutionException e) {
                 Rlog.e(TAG, "Failed to schedule onDisableNotifier: " + e.getMessage());
             }
@@ -158,15 +163,16 @@
     }
 
     /** Get a singleton CellularIdentifierDisclosureNotifier. */
-    public static synchronized CellularIdentifierDisclosureNotifier getInstance() {
+    public static synchronized CellularIdentifierDisclosureNotifier getInstance(
+            CellularNetworkSecuritySafetySource safetySource) {
         if (sInstance == null) {
-            sInstance = new CellularIdentifierDisclosureNotifier();
+            sInstance = new CellularIdentifierDisclosureNotifier(safetySource);
         }
 
         return sInstance;
     }
 
-    private Runnable incrementAndNotify(int subId) {
+    private Runnable incrementAndNotify(Context context, int subId) {
         return () -> {
             DisclosureWindow window = mWindows.get(subId);
             if (window == null) {
@@ -174,7 +180,7 @@
                 mWindows.put(subId, window);
             }
 
-            window.increment(this);
+            window.increment(context, this);
 
             int disclosureCount = window.getDisclosureCount();
 
@@ -185,31 +191,29 @@
                             + ". New disclosure count "
                             + disclosureCount);
 
-            // TODO (b/308985417) emit safety center issue
-            //            mSafetySource.setIdentifierDisclosure(
-            //                    subId,
-            //                    disclosureCount,
-            //                    window.getFirstOpen(),
-            //                    window.getCurrentEnd());
+            mSafetySource.setIdentifierDisclosure(
+                    context,
+                    subId,
+                    disclosureCount,
+                    window.getFirstOpen(),
+                    window.getCurrentEnd());
         };
     }
 
-    private Runnable onDisableNotifier() {
+    private Runnable onDisableNotifier(Context context) {
         return () -> {
             Rlog.d(TAG, "On disable notifier");
             for (DisclosureWindow window : mWindows.values()) {
                 window.close();
             }
-            // TODO (b/308985417) disable safety center issues
-            // mSafetySource.setIdentifierDisclosureIssueEnabled(false);
+            mSafetySource.setIdentifierDisclosureIssueEnabled(context, false);
         };
     }
 
-    private Runnable onEnableNotifier() {
+    private Runnable onEnableNotifier(Context context) {
         return () -> {
             Rlog.i(TAG, "On enable notifier");
-            // TODO (b/308985417) enable safety center issues
-            // mSafetySource.setIdentifierDisclosureIssueEnabled(true);
+            mSafetySource.setIdentifierDisclosureIssueEnabled(context, true);
         };
     }
 
@@ -262,7 +266,7 @@
      * A helper class that maintains all state associated with the disclosure window for a single
      * subId. No methods are thread safe. Callers must implement all synchronization.
      */
-    private static class DisclosureWindow {
+    private class DisclosureWindow {
         private int mDisclosureCount;
         private Instant mWindowFirstOpen;
         private Instant mLastEvent;
@@ -278,7 +282,7 @@
             mWhenWindowCloses = null;
         }
 
-        void increment(CellularIdentifierDisclosureNotifier notifier) {
+        void increment(Context context, CellularIdentifierDisclosureNotifier notifier) {
 
             mDisclosureCount++;
 
@@ -295,7 +299,7 @@
             try {
                 mWhenWindowCloses =
                         notifier.mSerializedWorkQueue.schedule(
-                                closeWindowRunnable(),
+                                closeWindowRunnable(context),
                                 notifier.mWindowCloseDuration,
                                 notifier.mWindowCloseUnit);
             } catch (RejectedExecutionException e) {
@@ -331,7 +335,7 @@
             mWhenWindowCloses = null;
         }
 
-        private Runnable closeWindowRunnable() {
+        private Runnable closeWindowRunnable(Context context) {
             return () -> {
                 Rlog.i(
                         TAG,
@@ -340,9 +344,7 @@
                                 + ". Disclosure count was "
                                 + getDisclosureCount());
                 close();
-
-                // TODO (b/308985417) clear safety center issue
-                // mSafetySource.setIdentifierDisclosure(mSubId, 0, null, null);
+                mSafetySource.clearIdentifierDisclosure(context, mSubId);
             };
         }
 
diff --git a/src/java/com/android/internal/telephony/security/CellularNetworkSecuritySafetySource.java b/src/java/com/android/internal/telephony/security/CellularNetworkSecuritySafetySource.java
new file mode 100644
index 0000000..34f26e3
--- /dev/null
+++ b/src/java/com/android/internal/telephony/security/CellularNetworkSecuritySafetySource.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2024 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.security;
+
+import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED;
+import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED;
+import static android.safetycenter.SafetySourceData.SEVERITY_LEVEL_INFORMATION;
+import static android.safetycenter.SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION;
+
+import android.annotation.IntDef;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.safetycenter.SafetyCenterManager;
+import android.safetycenter.SafetyEvent;
+import android.safetycenter.SafetySourceData;
+import android.safetycenter.SafetySourceIssue;
+import android.safetycenter.SafetySourceStatus;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.time.Instant;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/**
+ * Holds the state needed to report the Safety Center status and issues related to cellular
+ * network security.
+ */
+public class CellularNetworkSecuritySafetySource {
+    private static final String SAFETY_SOURCE_ID = "AndroidCellularNetworkSecurity";
+
+    private static final String NULL_CIPHER_ISSUE_NON_ENCRYPTED_ID = "null_cipher_non_encrypted";
+    private static final String NULL_CIPHER_ISSUE_ENCRYPTED_ID = "null_cipher_encrypted";
+
+    private static final String NULL_CIPHER_ACTION_SETTINGS_ID = "cellular_security_settings";
+    private static final String NULL_CIPHER_ACTION_LEARN_MORE_ID = "learn_more";
+
+    private static final String IDENTIFIER_DISCLOSURE_ISSUE_ID = "identifier_disclosure";
+
+    private static final Intent CELLULAR_NETWORK_SECURITY_SETTINGS_INTENT =
+            new Intent("android.settings.CELLULAR_NETWORK_SECURITY");
+    // TODO(b/321999913): direct to a help page URL e.g.
+    //                    new Intent(Intent.ACTION_VIEW, Uri.parse("https://..."));
+    private static final Intent LEARN_MORE_INTENT = new Intent();
+
+    static final int NULL_CIPHER_STATE_ENCRYPTED = 0;
+    static final int NULL_CIPHER_STATE_NOTIFY_ENCRYPTED = 1;
+    static final int NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED = 2;
+
+    @IntDef(
+        prefix = {"NULL_CIPHER_STATE_"},
+        value = {
+            NULL_CIPHER_STATE_ENCRYPTED,
+            NULL_CIPHER_STATE_NOTIFY_ENCRYPTED,
+            NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface NullCipherState {}
+
+    private static CellularNetworkSecuritySafetySource sInstance;
+
+    private final SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
+    private final SubscriptionManagerService mSubscriptionManagerService;
+
+    private boolean mNullCipherStateIssuesEnabled;
+    private HashMap<Integer, Integer> mNullCipherStates = new HashMap<>();
+
+    private boolean mIdentifierDisclosureIssuesEnabled;
+    private HashMap<Integer, IdentifierDisclosure> mIdentifierDisclosures = new HashMap<>();
+
+    /**
+     * Gets a singleton CellularNetworkSecuritySafetySource.
+     */
+    public static synchronized CellularNetworkSecuritySafetySource getInstance(Context context) {
+        if (sInstance == null) {
+            sInstance = new CellularNetworkSecuritySafetySource(
+                    new SafetyCenterManagerWrapper(context));
+        }
+        return sInstance;
+    }
+
+    @VisibleForTesting
+    public CellularNetworkSecuritySafetySource(
+            SafetyCenterManagerWrapper safetyCenterManagerWrapper) {
+        mSafetyCenterManagerWrapper = safetyCenterManagerWrapper;
+        mSubscriptionManagerService = SubscriptionManagerService.getInstance();
+    }
+
+    /** Enables or disables the null cipher issue and clears any current issues. */
+    public synchronized void setNullCipherIssueEnabled(Context context, boolean enabled) {
+        mNullCipherStateIssuesEnabled = enabled;
+        mNullCipherStates.clear();
+        updateSafetyCenter(context);
+    }
+
+    /** Sets the null cipher issue state for the identified subscription. */
+    public synchronized void setNullCipherState(
+            Context context, int subId, @NullCipherState int nullCipherState) {
+        mNullCipherStates.put(subId, nullCipherState);
+        updateSafetyCenter(context);
+    }
+
+    /**
+     * Enables or disables the identifier disclosure issue and clears any current issues if the
+     * enable state is changed.
+     */
+    public synchronized void setIdentifierDisclosureIssueEnabled(Context context, boolean enabled) {
+        // This check ensures that if we're enabled and we are asked to enable ourselves again (can
+        // happen if the modem restarts), we don't clear our state.
+        if (enabled != mIdentifierDisclosureIssuesEnabled) {
+            mIdentifierDisclosureIssuesEnabled = enabled;
+            mIdentifierDisclosures.clear();
+            updateSafetyCenter(context);
+        }
+    }
+
+    /** Sets the identifier disclosure issue state for the identifier subscription. */
+    public synchronized void setIdentifierDisclosure(
+            Context context, int subId, int count, Instant start, Instant end) {
+        IdentifierDisclosure disclosure = new IdentifierDisclosure(count, start, end);
+        mIdentifierDisclosures.put(subId, disclosure);
+        updateSafetyCenter(context);
+    }
+
+    /** Clears the identifier disclosure issue state for the identified subscription. */
+    public synchronized void clearIdentifierDisclosure(Context context, int subId) {
+        mIdentifierDisclosures.remove(subId);
+        updateSafetyCenter(context);
+    }
+
+    /** Refreshed the safety source in response to the identified broadcast. */
+    public synchronized void refresh(Context context, String refreshBroadcastId) {
+        mSafetyCenterManagerWrapper.setRefreshedSafetySourceData(
+                refreshBroadcastId, getSafetySourceData(context));
+    }
+
+    private void updateSafetyCenter(Context context) {
+        mSafetyCenterManagerWrapper.setSafetySourceData(getSafetySourceData(context));
+    }
+
+    private boolean isSafetySourceHidden() {
+        return !mNullCipherStateIssuesEnabled && !mIdentifierDisclosureIssuesEnabled;
+    }
+
+    private SafetySourceData getSafetySourceData(Context context) {
+        if (isSafetySourceHidden()) {
+            // The cellular network security safety source is configured with
+            // initialDisplayState="hidden"
+            return null;
+        }
+
+        Stream<Optional<SafetySourceIssue>> nullCipherIssues =
+                mNullCipherStates.entrySet().stream()
+                        .map(e -> getNullCipherIssue(context, e.getKey(), e.getValue()));
+        Stream<Optional<SafetySourceIssue>> identifierDisclosureIssues =
+                mIdentifierDisclosures.entrySet().stream()
+                        .map(e -> getIdentifierDisclosureIssue(context, e.getKey(), e.getValue()));
+        SafetySourceIssue[] issues = Stream.concat(nullCipherIssues, identifierDisclosureIssues)
+                .flatMap(Optional::stream)
+                .toArray(SafetySourceIssue[]::new);
+
+        SafetySourceData.Builder builder = new SafetySourceData.Builder();
+        int maxSeverity = SEVERITY_LEVEL_INFORMATION;
+        for (SafetySourceIssue issue : issues) {
+            builder.addIssue(issue);
+            maxSeverity = Math.max(maxSeverity, issue.getSeverityLevel());
+        }
+
+        builder.setStatus(
+                new SafetySourceStatus.Builder(
+                        context.getString(R.string.scCellularNetworkSecurityTitle),
+                        context.getString(R.string.scCellularNetworkSecuritySummary),
+                        maxSeverity)
+                    .setPendingIntent(mSafetyCenterManagerWrapper.getActivityPendingIntent(
+                            context, CELLULAR_NETWORK_SECURITY_SETTINGS_INTENT))
+                    .build());
+        return builder.build();
+    }
+
+    /** Builds the null cipher issue if it's enabled and there are null ciphers to report. */
+    private Optional<SafetySourceIssue> getNullCipherIssue(
+            Context context, int subId, @NullCipherState int state) {
+        if (!mNullCipherStateIssuesEnabled) {
+            return Optional.empty();
+        }
+
+        SubscriptionInfoInternal subInfo =
+                mSubscriptionManagerService.getSubscriptionInfoInternal(subId);
+        final SafetySourceIssue.Builder builder;
+        switch (state) {
+            case NULL_CIPHER_STATE_ENCRYPTED:
+                return Optional.empty();
+            case NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED:
+                builder = new SafetySourceIssue.Builder(
+                        NULL_CIPHER_ISSUE_NON_ENCRYPTED_ID + "_" + subId,
+                        context.getString(
+                            R.string.scNullCipherIssueNonEncryptedTitle, subInfo.getDisplayName()),
+                        context.getString(R.string.scNullCipherIssueNonEncryptedSummary),
+                        SEVERITY_LEVEL_RECOMMENDATION,
+                        NULL_CIPHER_ISSUE_NON_ENCRYPTED_ID);
+                break;
+            case NULL_CIPHER_STATE_NOTIFY_ENCRYPTED:
+                builder = new SafetySourceIssue.Builder(
+                        NULL_CIPHER_ISSUE_NON_ENCRYPTED_ID + "_" + subId,
+                        context.getString(
+                            R.string.scNullCipherIssueEncryptedTitle, subInfo.getDisplayName()),
+                        context.getString(R.string.scNullCipherIssueEncryptedSummary),
+                        SEVERITY_LEVEL_INFORMATION,
+                        NULL_CIPHER_ISSUE_ENCRYPTED_ID);
+                break;
+            default:
+                throw new AssertionError();
+        }
+
+        return Optional.of(
+                builder
+                    .setNotificationBehavior(SafetySourceIssue.NOTIFICATION_BEHAVIOR_IMMEDIATELY)
+                    .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE)
+                    .addAction(
+                        new SafetySourceIssue.Action.Builder(
+                                NULL_CIPHER_ACTION_SETTINGS_ID,
+                                context.getString(R.string.scNullCipherIssueActionSettings),
+                                mSafetyCenterManagerWrapper.getActivityPendingIntent(
+                                        context, CELLULAR_NETWORK_SECURITY_SETTINGS_INTENT))
+                            .build())
+                    .addAction(
+                        new SafetySourceIssue.Action.Builder(
+                                NULL_CIPHER_ACTION_LEARN_MORE_ID,
+                                context.getString(R.string.scNullCipherIssueActionLearnMore),
+                                mSafetyCenterManagerWrapper.getActivityPendingIntent(
+                                        context, LEARN_MORE_INTENT))
+                            .build())
+                    .build());
+    }
+
+    /** Builds the identity disclosure issue if it's enabled and there are disclosures to report. */
+    private Optional<SafetySourceIssue> getIdentifierDisclosureIssue(
+            Context context, int subId, IdentifierDisclosure disclosure) {
+        if (!mIdentifierDisclosureIssuesEnabled || disclosure.getDisclosureCount() == 0) {
+            return Optional.empty();
+        }
+
+        SubscriptionInfoInternal subInfo =
+                mSubscriptionManagerService.getSubscriptionInfoInternal(subId);
+        return Optional.of(
+                new SafetySourceIssue.Builder(
+                        IDENTIFIER_DISCLOSURE_ISSUE_ID + "_" + subId,
+                        context.getString(R.string.scIdentifierDisclosureIssueTitle),
+                        context.getString(
+                                R.string.scIdentifierDisclosureIssueSummary,
+                                disclosure.getDisclosureCount(),
+                                Date.from(disclosure.getWindowStart()),
+                                Date.from(disclosure.getWindowEnd()),
+                                subInfo.getDisplayName()),
+                        SEVERITY_LEVEL_RECOMMENDATION,
+                        IDENTIFIER_DISCLOSURE_ISSUE_ID)
+                    .setNotificationBehavior(SafetySourceIssue.NOTIFICATION_BEHAVIOR_IMMEDIATELY)
+                    .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE)
+                    .addAction(
+                        new SafetySourceIssue.Action.Builder(
+                                NULL_CIPHER_ACTION_SETTINGS_ID,
+                                context.getString(R.string.scNullCipherIssueActionSettings),
+                                mSafetyCenterManagerWrapper.getActivityPendingIntent(
+                                        context, CELLULAR_NETWORK_SECURITY_SETTINGS_INTENT))
+                            .build())
+                    .addAction(
+                        new SafetySourceIssue.Action.Builder(
+                                NULL_CIPHER_ACTION_LEARN_MORE_ID,
+                                context.getString(R.string.scNullCipherIssueActionLearnMore),
+                                mSafetyCenterManagerWrapper.getActivityPendingIntent(
+                                        context, LEARN_MORE_INTENT))
+                            .build())
+                .build());
+    }
+
+    /** A wrapper around {@link SafetyCenterManager} that can be instrumented in tests. */
+    @VisibleForTesting
+    public static class SafetyCenterManagerWrapper {
+        private final SafetyCenterManager mSafetyCenterManager;
+
+        public SafetyCenterManagerWrapper(Context context) {
+            mSafetyCenterManager = context.getSystemService(SafetyCenterManager.class);
+        }
+
+        /** Retrieve a {@link PendingIntent} that will start a new activity. */
+        public PendingIntent getActivityPendingIntent(Context context, Intent intent) {
+            return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
+        }
+
+        /** Set the {@link SafetySourceData} for this safety source. */
+        public void setSafetySourceData(SafetySourceData safetySourceData) {
+            mSafetyCenterManager.setSafetySourceData(
+                    SAFETY_SOURCE_ID,
+                    safetySourceData,
+                    new SafetyEvent.Builder(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build());
+        }
+
+        /** Sets the {@link SafetySourceData} in response to a refresh request. */
+        public void setRefreshedSafetySourceData(
+                String refreshBroadcastId, SafetySourceData safetySourceData) {
+            mSafetyCenterManager.setSafetySourceData(
+                    SAFETY_SOURCE_ID,
+                    safetySourceData,
+                    new SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED)
+                            .setRefreshBroadcastId(refreshBroadcastId)
+                            .build());
+        }
+    }
+
+    private static class IdentifierDisclosure {
+        private final int mDisclosureCount;
+        private final Instant mWindowStart;
+        private final Instant mWindowEnd;
+
+        private IdentifierDisclosure(int count, Instant start, Instant end) {
+            mDisclosureCount = count;
+            mWindowStart = start;
+            mWindowEnd  = end;
+        }
+
+        private int getDisclosureCount() {
+            return mDisclosureCount;
+        }
+
+        private Instant getWindowStart() {
+            return mWindowStart;
+        }
+
+        private Instant getWindowEnd() {
+            return mWindowEnd;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof IdentifierDisclosure)) {
+                return false;
+            }
+            IdentifierDisclosure other = (IdentifierDisclosure) o;
+            return mDisclosureCount == other.mDisclosureCount
+                    && Objects.equals(mWindowStart, other.mWindowStart)
+                    && Objects.equals(mWindowEnd, other.mWindowEnd);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mDisclosureCount, mWindowStart, mWindowEnd);
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
index bbe88a8..3d07d47 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
@@ -284,7 +284,16 @@
                     SubscriptionInfoInternal::getOnlyNonTerrestrialNetwork),
             new AbstractMap.SimpleImmutableEntry<>(
                     SimInfo.COLUMN_SERVICE_CAPABILITIES,
-                    SubscriptionInfoInternal::getServiceCapabilities)
+                    SubscriptionInfoInternal::getServiceCapabilities),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_TRANSFER_STATUS,
+                    SubscriptionInfoInternal::getTransferStatus),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
+                    SubscriptionInfoInternal::getSatelliteEntitlementStatus),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS,
+                    SubscriptionInfoInternal::getSatelliteEntitlementPlmns)
     );
 
     /**
@@ -418,7 +427,13 @@
                     SubscriptionDatabaseManager::setNtn),
             new AbstractMap.SimpleImmutableEntry<>(
                     SimInfo.COLUMN_SERVICE_CAPABILITIES,
-                    SubscriptionDatabaseManager::setServiceCapabilities)
+                    SubscriptionDatabaseManager::setServiceCapabilities),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_TRANSFER_STATUS,
+                    SubscriptionDatabaseManager::setTransferStatus),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
+                    SubscriptionDatabaseManager::setSatelliteEntitlementStatus)
     );
 
     /**
@@ -480,7 +495,10 @@
                     SubscriptionDatabaseManager::setNumberFromCarrier),
             new AbstractMap.SimpleImmutableEntry<>(
                     SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS,
-                    SubscriptionDatabaseManager::setNumberFromIms)
+                    SubscriptionDatabaseManager::setNumberFromIms),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS,
+                    SubscriptionDatabaseManager::setSatelliteEntitlementPlmns)
     );
 
     /**
@@ -2099,6 +2117,51 @@
     }
 
     /**
+     * Set whether satellite entitlement status is enabled by entitlement query result.
+     *
+     * @param subId Subscription id.
+     * @param isSatelliteEntitlementStatus Whether satellite entitlement status is enabled or
+     * disabled.
+     * @throws IllegalArgumentException if the subscription does not exist.
+     */
+    public void setSatelliteEntitlementStatus(int subId,
+            int isSatelliteEntitlementStatus) {
+        writeDatabaseAndCacheHelper(subId,
+                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
+                isSatelliteEntitlementStatus,
+                SubscriptionInfoInternal.Builder::setSatelliteEntitlementStatus);
+    }
+
+    /**
+     * Set satellite entitlement plmns by entitlement query result.
+     *
+     * @param subId Subscription id.
+     * @param satelliteEntitlementPlmns Satellite entitlement plmns
+     * @throws IllegalArgumentException if the subscription does not exist.
+     */
+    public void setSatelliteEntitlementPlmns(int subId,
+            @NonNull String satelliteEntitlementPlmns) {
+        writeDatabaseAndCacheHelper(subId,
+                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS,
+                satelliteEntitlementPlmns,
+                SubscriptionInfoInternal.Builder::setSatelliteEntitlementPlmns);
+    }
+
+    /**
+     * Set satellite entitlement plmn list by entitlement query result.
+     *
+     * @param subId Subscription id.
+     * @param satelliteEntitlementPlmnList Satellite entitlement plmn list
+     * @throws IllegalArgumentException if the subscription does not exist.
+     */
+    public void setSatelliteEntitlementPlmnList(int subId,
+            @NonNull List<String> satelliteEntitlementPlmnList) {
+        String satelliteEntitlementPlmns = satelliteEntitlementPlmnList.stream().collect(
+                Collectors.joining(","));
+        setSatelliteEntitlementPlmns(subId, satelliteEntitlementPlmns);
+    }
+
+    /**
      * 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.
@@ -2330,11 +2393,21 @@
                                 SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER)))
                 .setServiceCapabilities(cursor.getInt(
                         cursor.getColumnIndexOrThrow(
-                                SimInfo.COLUMN_SERVICE_CAPABILITIES)));
+                                SimInfo.COLUMN_SERVICE_CAPABILITIES)))
+                .setSatelliteEntitlementStatus(cursor.getInt(
+                        cursor.getColumnIndexOrThrow(
+                                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS)))
+                .setSatelliteEntitlementPlmns(cursor.getString(
+                        cursor.getColumnIndexOrThrow(
+                                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS)));
         if (mFeatureFlags.oemEnabledSatelliteFlag()) {
             builder.setOnlyNonTerrestrialNetwork(cursor.getInt(cursor.getColumnIndexOrThrow(
                     SimInfo.COLUMN_IS_NTN)));
         }
+        if (mFeatureFlags.supportPsimToEsimConversion()) {
+            builder.setTransferStatus(cursor.getInt(cursor.getColumnIndexOrThrow(
+                    SimInfo.COLUMN_TRANSFER_STATUS)));
+        }
         return builder.build();
     }
 
@@ -2409,6 +2482,25 @@
     }
 
     /**
+     * Set the transfer status of the subscriptionInfo that corresponds to subId.
+     *
+     * @param subId Subscription ID.
+     * @param status The transfer status to change.
+     *
+     * @throws IllegalArgumentException if the subscription does not exist.
+     */
+    public void setTransferStatus(int subId, int status) {
+        if (!mFeatureFlags.supportPsimToEsimConversion()) {
+            log("SubscriptionDatabaseManager:supportPsimToEsimConversion is false");
+            return;
+        }
+
+        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_TRANSFER_STATUS,
+                status,
+                SubscriptionInfoInternal.Builder::setTransferStatus);
+    }
+
+    /**
      * Log debug messages.
      *
      * @param s debug messages
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java b/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
index c6fc23d..82af4e8 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
@@ -444,7 +444,7 @@
 
     /**
      * Whether satellite attach for carrier is enabled or disabled by user.
-     * By default, its disabled. It is intended to use integer to fit the database format.
+     * By default, its enabled. It is intended to use integer to fit the database format.
      */
     private final int mIsSatelliteAttachEnabledForCarrier;
 
@@ -474,6 +474,23 @@
     private final int mServiceCapabilities;
 
     /**
+     * The transfer status of the subscription
+     */
+    private final int mTransferStatus;
+
+    /**
+     * Whether satellite entitlement status is enabled or disabled by the entitlement query result.
+     * By default, its disabled. It is intended to use integer to fit the database format.
+     */
+    private final int mIsSatelliteEntitlementStatus;
+
+    /**
+     * The satellite entitlement plmns based on the entitlement query results
+     * By default, its empty. It is intended to use string to fit the database format.
+     */
+    @NonNull private final String mSatelliteEntitlementPlmns;
+
+    /**
      * Constructor from builder.
      *
      * @param builder Builder of {@link SubscriptionInfoInternal}.
@@ -549,6 +566,9 @@
         this.mCardId = builder.mCardId;
         this.mIsGroupDisabled = builder.mIsGroupDisabled;
         this.mServiceCapabilities = builder.mServiceCapabilities;
+        this.mTransferStatus = builder.mTransferStatus;
+        this.mIsSatelliteEntitlementStatus = builder.mIsSatelliteEntitlementStatus;
+        this.mSatelliteEntitlementPlmns = builder.mSatelliteEntitlementPlmns;
     }
 
     /**
@@ -621,7 +641,8 @@
      * @return the number of this subscription.
      */
     public String getNumber() {
-        return mNumber;
+        if (TextUtils.isEmpty(mNumberFromCarrier)) return mNumber;
+        return mNumberFromCarrier;
     }
 
     /**
@@ -1205,6 +1226,29 @@
     public int getServiceCapabilities() {
         return mServiceCapabilities;
     }
+    /**
+     * @return Transfer status.
+     */
+    public int getTransferStatus() {
+        return mTransferStatus;
+    }
+
+    /**
+     * @return {@code 1} if satellite entitlement status is enabled by entitlement query result.
+     */
+    public int getSatelliteEntitlementStatus() {
+        return mIsSatelliteEntitlementStatus;
+    }
+
+    /**
+     * @return Satellite entitlement plmns is empty or not by entitlement query result.
+     *
+     * For example, "123123, 12310" or ""
+     */
+    @NonNull
+    public String getSatelliteEntitlementPlmns() {
+        return mSatelliteEntitlementPlmns;
+    }
 
     /** @return converted {@link SubscriptionInfo}. */
     @NonNull
@@ -1217,7 +1261,7 @@
                 .setCarrierName(mCarrierName)
                 .setDisplayNameSource(mDisplayNameSource)
                 .setIconTint(mIconTint)
-                .setNumber(mNumber)
+                .setNumber(getNumber())
                 .setDataRoaming(mDataRoaming)
                 .setMcc(mMcc)
                 .setMnc(mMnc)
@@ -1244,6 +1288,7 @@
                 .setOnlyNonTerrestrialNetwork(mIsOnlyNonTerrestrialNetwork == 1)
                 .setServiceCapabilities(
                         SubscriptionManager.getServiceCapabilitiesSet(mServiceCapabilities))
+                .setTransferStatus(mTransferStatus)
                 .build();
     }
 
@@ -1264,7 +1309,7 @@
                 + " displayNameSource="
                 + SubscriptionManager.displayNameSourceToString(mDisplayNameSource)
                 + " iconTint=" + mIconTint
-                + " number=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mNumber)
+                + " number=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, getNumber())
                 + " dataRoaming=" + mDataRoaming
                 + " mcc=" + mMcc
                 + " mnc=" + mMnc
@@ -1304,6 +1349,9 @@
                 + " getOnlyNonTerrestrialNetwork=" + mIsOnlyNonTerrestrialNetwork
                 + " isGroupDisabled=" + mIsGroupDisabled
                 + " serviceCapabilities=" + mServiceCapabilities
+                + " transferStatus=" + mTransferStatus
+                + " satelliteEntitlementStatus=" + mIsSatelliteEntitlementStatus
+                + " satelliteEntitlementPlmns=" + mSatelliteEntitlementPlmns
                 + "]";
     }
 
@@ -1361,7 +1409,10 @@
                 that.mNumberFromCarrier) && mNumberFromIms.equals(that.mNumberFromIms)
                 && mIsSatelliteAttachEnabledForCarrier == that.mIsSatelliteAttachEnabledForCarrier
                 && mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork
-                && mServiceCapabilities == that.mServiceCapabilities;
+                && mServiceCapabilities == that.mServiceCapabilities
+                && mTransferStatus == that.mTransferStatus
+                && mIsSatelliteEntitlementStatus == that.mIsSatelliteEntitlementStatus
+                && mSatelliteEntitlementPlmns == that.mSatelliteEntitlementPlmns;
     }
 
     @Override
@@ -1384,7 +1435,8 @@
                 mNumberFromIms, mPortIndex, mUsageSetting, mLastUsedTPMessageReference, mUserId,
                 mIsSatelliteEnabled, mCardId, mIsGroupDisabled,
                 mIsSatelliteAttachEnabledForCarrier, mIsOnlyNonTerrestrialNetwork,
-                mServiceCapabilities);
+                mServiceCapabilities, mTransferStatus, mIsSatelliteEntitlementStatus,
+                mSatelliteEntitlementPlmns);
         result = 31 * result + Arrays.hashCode(mNativeAccessRules);
         result = 31 * result + Arrays.hashCode(mCarrierConfigAccessRules);
         result = 31 * result + Arrays.hashCode(mRcsConfig);
@@ -1750,7 +1802,7 @@
         /**
          * Whether satellite attach for carrier is enabled by user.
          */
-        private int mIsSatelliteAttachEnabledForCarrier = 0;
+        private int mIsSatelliteAttachEnabledForCarrier = 1;
 
         /**
          * Whether this subscription is used for communicating with non-terrestrial network or not.
@@ -1777,6 +1829,22 @@
         private int mServiceCapabilities;
 
         /**
+         * The transfer status of the subscription
+         */
+        private int mTransferStatus;
+
+        /**
+         * Whether satellite entitlement status is enabled by entitlement query result.
+         */
+        private int mIsSatelliteEntitlementStatus = 0;
+
+        /**
+         * Whether satellite entitlement plmns is empty or not by entitlement query result.
+         */
+        @NonNull
+        private String mSatelliteEntitlementPlmns = "";
+
+        /**
          * Default constructor.
          */
         public Builder() {
@@ -1855,6 +1923,9 @@
             mCardId = info.mCardId;
             mIsGroupDisabled = info.mIsGroupDisabled;
             mServiceCapabilities = info.mServiceCapabilities;
+            mTransferStatus = info.mTransferStatus;
+            mIsSatelliteEntitlementStatus = info.mIsSatelliteEntitlementStatus;
+            mSatelliteEntitlementPlmns = info.mSatelliteEntitlementPlmns;
         }
 
         /**
@@ -2787,6 +2858,44 @@
         }
 
         /**
+         * Set the transfer status of the subscription.
+         *
+         * @param status The transfer status
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setTransferStatus(int status) {
+            mTransferStatus = status;
+            return this;
+        }
+
+        /**
+         * Set whether satellite entitlement status is enabled by entitlement query result.
+         *
+         * @param isSatelliteEntitlementStatus {@code 1} if satellite entitlement status is
+         * enabled by entitlement query result.
+         * @return The builder
+         */
+        @NonNull
+        public Builder setSatelliteEntitlementStatus(int isSatelliteEntitlementStatus) {
+            mIsSatelliteEntitlementStatus = isSatelliteEntitlementStatus;
+            return this;
+        }
+
+        /**
+         * Set whether satellite entitlement plmns is empty or not by entitlement query result.
+         *
+         * @param satelliteEntitlementPlmns satellite entitlement plmns is empty or not by
+         * entitlement query result.
+         * @return The builder
+         */
+        @NonNull
+        public Builder setSatelliteEntitlementPlmns(@NonNull String satelliteEntitlementPlmns) {
+            mSatelliteEntitlementPlmns = satelliteEntitlementPlmns;
+            return this;
+        }
+
+        /**
          * Build the {@link SubscriptionInfoInternal}.
          *
          * @return The {@link SubscriptionInfoInternal} instance.
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
index ddf80a8..8757c97 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
@@ -22,6 +22,7 @@
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.ColorInt;
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -186,7 +187,9 @@
             SimInfo.COLUMN_NR_ADVANCED_CALLING_ENABLED,
             SimInfo.COLUMN_SATELLITE_ENABLED,
             SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
-            SimInfo.COLUMN_IS_NTN
+            SimInfo.COLUMN_IS_NTN,
+            SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
+            SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS
     );
 
     /**
@@ -922,6 +925,26 @@
     }
 
     /**
+     * Set the group owner on the subscription
+     *
+     * <p> Note: This only sets the group owner field and doesn't update other relevant fields.
+     * Prefer to call {@link #addSubscriptionsIntoGroup}.
+     *
+     * @param subId Subscription id.
+     * @param groupOwner The group owner to assign to the subscription
+     */
+    public void setGroupOwner(int subId, @NonNull String groupOwner) {
+        // This can throw IllegalArgumentException if the subscription does not exist.
+        try {
+            mSubscriptionDatabaseManager.setGroupOwner(
+                    subId,
+                    groupOwner);
+        } catch (IllegalArgumentException e) {
+            loge("setManaged: invalid subId=" + subId);
+        }
+    }
+
+    /**
      * Set last used TP message reference.
      *
      * @param subId Subscription id.
@@ -1219,7 +1242,11 @@
                         builder.setCardString(mUiccController.convertToCardString(cardId));
                     }
 
+                    if (mFeatureFlags.supportPsimToEsimConversion()) {
+                        builder.setTransferStatus(subInfo.getTransferStatus());
+                    }
                     embeddedSubs.add(subInfo.getSubscriptionId());
+
                     subInfo = builder.build();
                     log("updateEmbeddedSubscriptions: update subscription " + subInfo);
                     mSubscriptionDatabaseManager.updateSubscription(subInfo);
@@ -2161,6 +2188,7 @@
      * @return 0 if success, < 0 on error
      *
      * @throws SecurityException if the caller does not have required permissions.
+     * @throws IllegalArgumentException if {@code slotIndex} is invalid.
      */
     @Override
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -2174,6 +2202,11 @@
 
         enforceTelephonyFeatureWithException(getCurrentPackageName(), "addSubInfo");
 
+        if (!SubscriptionManager.isValidSlotIndex(slotIndex)
+                && slotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+            throw new IllegalArgumentException("Invalid slotIndex " + slotIndex);
+        }
+
         // Now that all security checks passes, perform the operation as ourselves.
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -3911,12 +3944,31 @@
     }
 
     /**
+     * Returns whether the given subscription is associated with the calling user.
+     *
+     * @param subscriptionId the subscription ID of the subscription
+     * @return {@code true} if the subscription is associated with the user that the calling process
+     *         is running in; {@code false} otherwise.
+     *
+     * @throws IllegalArgumentException if subscription doesn't exist.
+     * @throws SecurityException if the caller doesn't have permissions required.
+     */
+    @Override
+    public boolean isSubscriptionAssociatedWithCallingUser(int subscriptionId) {
+        enforcePermissions("isSubscriptionAssociatedWithCallingUser",
+                Manifest.permission.READ_PHONE_STATE);
+
+        UserHandle myUserHandle = UserHandle.of(UserHandle.getCallingUserId());
+        return mFeatureFlags.subscriptionUserAssociationQuery()
+            && isSubscriptionAssociatedWithUserNoCheck(subscriptionId, myUserHandle);
+    }
+
+    /**
      * Check if subscription and user are associated with each other.
      *
      * @param subscriptionId the subId of the subscription
      * @param userHandle user handle of the user
      * @return {@code true} if subscription is associated with user
-     * {@code true} if there are no subscriptions on device
      * else {@code false} if subscription is not associated with user.
      *
      * @throws SecurityException if the caller doesn't have permissions required.
@@ -3927,6 +3979,12 @@
             @NonNull UserHandle userHandle) {
         enforcePermissions("isSubscriptionAssociatedWithUser",
                 Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
+
+        return isSubscriptionAssociatedWithUserNoCheck(subscriptionId, userHandle);
+    }
+
+    private boolean isSubscriptionAssociatedWithUserNoCheck(int subscriptionId,
+            @NonNull UserHandle userHandle) {
         SubscriptionInfoInternal subInfoInternal = mSubscriptionDatabaseManager
                 .getSubscriptionInfoInternal(subscriptionId);
         // Throw IAE if no record of the sub's association state.
@@ -4299,6 +4357,70 @@
         }
     }
 
+
+
+    /**
+     * Set the transfer status of the subscriptionInfo that corresponds to subId.
+     * @param subId The unique SubscriptionInfo key in database.
+     * @param status The transfer status to change. This value must be one of the following.
+     * {@link SubscriptionManager#TRANSFER_STATUS_NONE},
+     * {@link SubscriptionManager#TRANSFER_STATUS_TRANSFERRED_OUT} or
+     * {@link SubscriptionManager#TRANSFER_STATUS_CONVERTED}
+     *
+     */
+    @Override
+    @EnforcePermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+    public void setTransferStatus(int subId, int status) {
+        setTransferStatus_enforcePermission();
+        if (mContext.checkCallingOrSelfPermission(
+                Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must have WRITE_EMBEDDED_SUBSCRIPTIONS to"
+                    + "setTransferStatus");
+        }
+        long token = Binder.clearCallingIdentity();
+        try {
+            mSubscriptionDatabaseManager.setTransferStatus(subId, status);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Set the satellite entitlement plmn list value in the subscription database.
+     *
+     * @param subId subscription id.
+     * @param satelliteEntitlementPlmnList satellite entitlement plmn list
+     */
+    public void setSatelliteEntitlementPlmnList(int subId,
+            @NonNull List<String> satelliteEntitlementPlmnList) {
+        try {
+            mSubscriptionDatabaseManager.setSatelliteEntitlementPlmnList(
+                    subId, satelliteEntitlementPlmnList);
+        } catch (IllegalArgumentException e) {
+            loge("setSatelliteEntitlementPlmnList: invalid subId=" + subId);
+        }
+    }
+
+    /**
+     * Get the satellite entitlement plmn list value from the subscription database.
+     *
+     * @param subId subscription id.
+     * @return satellite entitlement plmn list
+     */
+    @NonNull
+    public List<String> getSatelliteEntitlementPlmnList(int subId) {
+        SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager.getSubscriptionInfoInternal(
+                subId);
+        if (subInfo == null) {
+            loge("getSatelliteEntitlementPlmnList: invalid subId=" + subId);
+            return new ArrayList<>();
+        }
+
+        return Arrays.stream(subInfo.getSatelliteEntitlementPlmns().split(",")).collect(
+                Collectors.toList());
+    }
+
     /**
      * Get the current calling package name.
      * @return the current calling package name
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
index da3920e..bef8944 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
@@ -34,6 +34,7 @@
 import android.app.KeyguardManager;
 import android.app.NotificationManager;
 import android.app.UiModeManager;
+import android.app.admin.DevicePolicyManager;
 import android.app.usage.UsageStatsManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -310,6 +311,8 @@
                     return mNetworkPolicyManager;
                 case Context.TELEPHONY_IMS_SERVICE:
                     return mImsManager;
+                case Context.DEVICE_POLICY_SERVICE:
+                    return mDevicePolicyManager;
                 default:
                     return null;
             }
@@ -357,6 +360,8 @@
                 return Context.EUICC_SERVICE;
             } else if (serviceClass == AlarmManager.class) {
                 return Context.ALARM_SERVICE;
+            } else if (serviceClass == DevicePolicyManager.class) {
+                return Context.DEVICE_POLICY_SERVICE;
             }
             return super.getSystemServiceName(serviceClass);
         }
@@ -731,6 +736,7 @@
     private final VcnManager mVcnManager = mock(VcnManager.class);
     private final NetworkPolicyManager mNetworkPolicyManager = mock(NetworkPolicyManager.class);
     private final ImsManager mImsManager = mock(ImsManager.class);
+    private final DevicePolicyManager mDevicePolicyManager = mock(DevicePolicyManager.class);
     private final Configuration mConfiguration = new Configuration();
     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
     private final SharedPreferences mSharedPreferences = PreferenceManager
diff --git a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
index c4b957e..a13a92c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
@@ -130,9 +130,12 @@
                     + UserHandle.USER_NULL + ","
                     + Telephony.SimInfo.COLUMN_SATELLITE_ENABLED + " INTEGER DEFAULT 0,"
                     + Telephony.SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER
-                    + " INTEGER DEFAULT 0, "
+                    + " INTEGER DEFAULT 1, "
                     + Telephony.SimInfo.COLUMN_IS_NTN + " INTEGER DEFAULT 0,"
-                    + Telephony.SimInfo.COLUMN_SERVICE_CAPABILITIES + " INTEGER DEFAULT 7"
+                    + Telephony.SimInfo.COLUMN_SERVICE_CAPABILITIES + " INTEGER DEFAULT 7,"
+                    + Telephony.SimInfo.COLUMN_TRANSFER_STATUS + " INTEGER DEFAULT 0,"
+                    + Telephony.SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS + " INTEGER DEFAULT 0,"
+                    + Telephony.SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS + " TEXT"
                     + ");";
         }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
index c1f0c5f..7de75ae 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
@@ -75,7 +75,7 @@
         mSimulatedCommands.setRadioPower(true, null);
         mPhone.mCi = this.mSimulatedCommands;
 
-        mCTUT = new GsmCdmaCallTracker(mPhone);
+        mCTUT = new GsmCdmaCallTracker(mPhone, mFeatureFlags);
         logd("GsmCdmaCallTracker initiated, waiting for Power on");
         /* Make sure radio state is power on before dial.
          * When radio state changed from off to on, CallTracker
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
index a903a34..e493a18 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
@@ -2868,7 +2868,10 @@
 
     @Test
     public void testCellularIdentifierDisclosure_disclosureEventAddedToNotifier() {
+        int phoneId = 0;
+        int subId = 10;
         when(mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()).thenReturn(true);
+        when(mSubscriptionManagerService.getSubId(phoneId)).thenReturn(subId);
 
         Phone phoneUT =
                 new GsmCdmaPhone(
@@ -2876,7 +2879,7 @@
                         mMockCi,
                         mNotifier,
                         true,
-                        0,
+                        phoneId,
                         PhoneConstants.PHONE_TYPE_GSM,
                         mTelephonyComponentFactory,
                         (c, p) -> mImsManager,
@@ -2895,20 +2898,22 @@
         processAllMessages();
 
         verify(mIdentifierDisclosureNotifier, times(1))
-                .addDisclosure(eq(mPhoneUT.getSubId()), eq(disclosure));
+                .addDisclosure(eq(mContext), eq(subId), eq(disclosure));
     }
 
     @Test
     public void testCellularIdentifierDisclosure_disclosureEventNull() {
+        int phoneId = 4;
+        int subId = 6;
         when(mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()).thenReturn(true);
-
+        when(mSubscriptionManagerService.getSubId(phoneId)).thenReturn(subId);
         Phone phoneUT =
                 new GsmCdmaPhone(
                         mContext,
                         mMockCi,
                         mNotifier,
                         true,
-                        0,
+                        phoneId,
                         PhoneConstants.PHONE_TYPE_GSM,
                         mTelephonyComponentFactory,
                         (c, p) -> mImsManager,
@@ -2920,7 +2925,7 @@
         processAllMessages();
 
         verify(mIdentifierDisclosureNotifier, never())
-                .addDisclosure(eq(mPhoneUT.getSubId()), any(CellularIdentifierDisclosure.class));
+                .addDisclosure(eq(mContext), eq(subId), any(CellularIdentifierDisclosure.class));
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
index 0869bab..1ea989a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -42,6 +43,9 @@
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
 import android.telephony.data.DataCallResponse;
+import android.telephony.data.EpsQos;
+import android.telephony.data.Qos;
+import android.telephony.data.QosBearerSession;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -57,6 +61,7 @@
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 @RunWith(AndroidTestingRunner.class)
@@ -268,7 +273,7 @@
         assertEquals("DefaultState", getCurrentState().getName());
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null));
+                DataCallResponse.LINK_STATUS_DORMANT);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
@@ -285,7 +290,9 @@
 
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         setPhysicalLinkStatus(false);
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null,
+                        mPhone.getServiceStateTracker().getPhysicalChannelConfigList(), null));
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
@@ -305,7 +312,7 @@
 
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null));
+                DataCallResponse.LINK_STATUS_DORMANT);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
@@ -316,7 +323,7 @@
         assertEquals("DefaultState", getCurrentState().getName());
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null));
+                DataCallResponse.LINK_STATUS_ACTIVE);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_con", getCurrentState().getName());
@@ -335,7 +342,9 @@
 
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         setPhysicalLinkStatus(true);
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null,
+                        mPhone.getServiceStateTracker().getPhysicalChannelConfigList(), null));
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_con", getCurrentState().getName());
@@ -356,7 +365,7 @@
 
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null));
+                DataCallResponse.LINK_STATUS_ACTIVE);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_con", getCurrentState().getName());
@@ -621,32 +630,37 @@
         physicalChannelConfigs.add(pcc2);
         doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
 
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         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 */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, new ArrayList<>(), null));
         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 */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         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 */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
         assertEquals("connected", getCurrentState().getName());
 
         physicalChannelConfigs.add(pcc2);
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
         assertEquals("connected_mmwave", getCurrentState().getName());
     }
@@ -680,20 +694,23 @@
         physicalChannelConfigs.add(pcc2);
         doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
 
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         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 */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, new ArrayList<>(), null));
         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 */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
         assertEquals("connected", getCurrentState().getName());
     }
@@ -729,19 +746,22 @@
         physicalChannelConfigs.add(pcc2);
         doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
 
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         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 */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, new ArrayList<>(), null));
         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 */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
         assertEquals("connected_mmwave", getCurrentState().getName());
     }
@@ -752,7 +772,7 @@
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
 
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null));
+                DataCallResponse.LINK_STATUS_ACTIVE);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_con", getCurrentState().getName());
@@ -769,7 +789,9 @@
         testTransitionToCurrentStateNrConnectedMmwave();
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         setPhysicalLinkStatus(true);
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null,
+                        mPhone.getServiceStateTracker().getPhysicalChannelConfigList(), null));
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
 
         processAllMessages();
@@ -790,7 +812,7 @@
         testTransitionToCurrentStateNrConnectedMmwave();
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null));
+                DataCallResponse.LINK_STATUS_ACTIVE);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
 
         processAllMessages();
@@ -806,7 +828,9 @@
 
         doReturn(true).when(mServiceState).isUsingCarrierAggregation();
         doReturn(new int[] {30000}).when(mServiceState).getCellBandwidths();
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null,
+                        mPhone.getServiceStateTracker().getPhysicalChannelConfigList(), null));
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
@@ -824,7 +848,7 @@
         // Transition to LTE connected state
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null));
+                DataCallResponse.LINK_STATUS_ACTIVE);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_con", getCurrentState().getName());
@@ -834,7 +858,9 @@
         // LTE -> LTE+
         doReturn(true).when(mServiceState).isUsingCarrierAggregation();
         doReturn(new int[] {30000}).when(mServiceState).getCellBandwidths();
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null,
+                        mPhone.getServiceStateTracker().getPhysicalChannelConfigList(), null));
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
@@ -852,7 +878,7 @@
         // Transition to idle state
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null));
+                DataCallResponse.LINK_STATUS_DORMANT);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
@@ -862,7 +888,9 @@
         // LTE -> LTE+
         doReturn(true).when(mServiceState).isUsingCarrierAggregation();
         doReturn(new int[] {30000}).when(mServiceState).getCellBandwidths();
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null,
+                        mPhone.getServiceStateTracker().getPhysicalChannelConfigList(), null));
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
@@ -874,7 +902,7 @@
         testTransitionToCurrentStateLteConnected();
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null));
+                DataCallResponse.LINK_STATUS_DORMANT);
 
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
@@ -891,7 +919,9 @@
         testTransitionToCurrentStateLteConnectedSupportPhysicalChannelConfig1_6();
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
         setPhysicalLinkStatus(false);
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null,
+                        mPhone.getServiceStateTracker().getPhysicalChannelConfigList(), null));
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
@@ -911,7 +941,7 @@
         testTransitionToCurrentStateLteConnected_usingUserDataForRrcDetection();
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null));
+                DataCallResponse.LINK_STATUS_DORMANT);
 
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
@@ -932,15 +962,32 @@
 
     @Test
     public void testEventRadioOffOrUnavailable() throws Exception {
-        testTransitionToCurrentStateNrConnected();
-        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
-                mNetworkTypeController.getOverrideNetworkType());
+        mBundle.putBoolean(CarrierConfigManager.KEY_RATCHET_NR_ADVANCED_BANDWIDTH_IF_RRC_IDLE_BOOL,
+                true);
+        testTransitionToCurrentStateNrConnectedMmwaveWithAdditionalBandAndNoMmwaveNrNsa();
 
+        // Radio off
         doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
         mNetworkTypeController.sendMessage(9 /* EVENT_RADIO_OFF_OR_UNAVAILABLE */);
         processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
                 mNetworkTypeController.getOverrideNetworkType());
+
+        // NR connected: Primary serving NR PCC with cell ID = 1, band = none
+        PhysicalChannelConfig pcc = new PhysicalChannelConfig.Builder()
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setPhysicalCellId(1)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .build();
+
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, List.of(pcc), null));
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
+        processAllMessages();
+        assertEquals("connected", getCurrentState().getName());
     }
 
     @Test
@@ -992,6 +1039,54 @@
     }
 
     @Test
+    public void testPrimaryTimerNetworkTypeChanged() throws Exception {
+        doAnswer(invocation -> {
+            doReturn(new TelephonyDisplayInfo(
+                    mNetworkTypeController.getDataNetworkType(),
+                    mNetworkTypeController.getOverrideNetworkType(),
+                    false)).when(mDisplayInfoController).getTelephonyDisplayInfo();
+            return null;
+        }).when(mDisplayInfoController).updateTelephonyDisplayInfo();
+        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_NR)
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .build();
+        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
+                anyInt(), anyInt());
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        sendCarrierConfigChanged();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // trigger 10 second timer after disconnecting from NR advanced
+        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .build();
+        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
+                anyInt(), anyInt());
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
+    }
+
+    @Test
     public void testPrimaryTimerDeviceIdleMode() throws Exception {
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
         mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
@@ -1339,6 +1434,89 @@
     }
 
     @Test
+    public void testSecondaryTimerAdvanceBand() throws Exception {
+        doReturn(true).when(mFeatureFlags).supportNrSaRrcIdle();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(mServiceState).getNrFrequencyRange();
+        ArrayList<PhysicalChannelConfig> physicalChannelConfigs = new ArrayList<>();
+        // use advanced band
+        physicalChannelConfigs.add(new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(1)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .setBand(41)
+                .build());
+        doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
+        mBundle.putIntArray(CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY,
+                new int[]{41});
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10");
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,5");
+        mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_BANDS_SECONDARY_TIMER_SECONDS_INT,
+                20);
+        sendCarrierConfigChanged();
+
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // lost the advance band, trigger 10 second connected_mmwave -> connected primary timer
+        physicalChannelConfigs.clear();
+        physicalChannelConfigs.add(new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(1)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .build());
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // switch to connected_rrc_idle before primary timer expires
+        physicalChannelConfigs.clear();
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
+        processAllMessages();
+
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // primary timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        // should trigger 20(not 5) seconds connected_mmwave -> connected_rrc_idle secondary timer
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // 5 seconds passed during connected_mmwave -> connected_rrc_idle secondary timer
+        moveTimeForward(5 * 1000);
+        processAllMessages();
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // secondary timer expired
+        moveTimeForward(15 * 1000);
+        processAllMessages();
+
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
+    }
+
+    @Test
     public void testSecondaryTimerExpireNrIdle() throws Exception {
         doReturn(true).when(mFeatureFlags).supportNrSaRrcIdle();
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
@@ -1369,7 +1547,8 @@
                 .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
                 .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
                 .build());
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
 
         assertEquals("connected", getCurrentState().getName());
@@ -1379,7 +1558,8 @@
 
         // switch to connected_rrc_idle
         physicalChannelConfigs.clear();
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
 
         assertEquals("connected_rrc_idle", getCurrentState().getName());
@@ -1438,7 +1618,8 @@
                 .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
                 .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
                 .build());
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
 
         assertEquals("connected", getCurrentState().getName());
@@ -1448,7 +1629,8 @@
 
         // switch to connected_rrc_idle
         physicalChannelConfigs.clear();
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
 
         assertEquals("connected_rrc_idle", getCurrentState().getName());
@@ -1473,7 +1655,8 @@
                 .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
                 .setBand(41)
                 .build());
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
 
         // secondary timer expires
         moveTimeForward(30 * 1000);
@@ -1517,7 +1700,8 @@
                 .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
                 .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
                 .build());
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
 
         assertEquals("connected", getCurrentState().getName());
@@ -1527,7 +1711,8 @@
 
         // switch to connected_rrc_idle
         physicalChannelConfigs.clear();
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
 
         assertEquals("connected_rrc_idle", getCurrentState().getName());
@@ -1552,7 +1737,8 @@
                 .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
                 .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
                 .build());
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
 
         assertEquals("connected_rrc_idle", getCurrentState().getName());
@@ -1611,7 +1797,7 @@
 
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null));
+                DataCallResponse.LINK_STATUS_ACTIVE);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
 
@@ -1677,6 +1863,91 @@
         assertTrue(mNetworkTypeController.areAnyTimersActive());
     }
 
+    @Test
+    public void testNrTimerResetWhenPlmnChanged() throws Exception {
+        testTransitionToCurrentStateNrConnectedMmwave();
+        mBundle.putBoolean(CarrierConfigManager.KEY_NR_TIMERS_RESET_ON_PLMN_CHANGE_BOOL,
+                true);
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,30");
+        sendCarrierConfigChanged();
+
+        // should trigger 10 second primary timer
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_UNKNOWN).when(mServiceState).getNrFrequencyRange();
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // PLMN changed, should cancel any active timers
+        ServiceState newSS = mock(ServiceState.class);
+        doReturn("different plmn").when(newSS).getOperatorNumeric();
+        doReturn(newSS).when(mSST).getServiceState();
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
+        processAllMessages();
+
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
+    }
+
+    @Test
+    public void testNrTimerResetWhenVoiceQos() throws Exception {
+        testTransitionToCurrentStateNrConnectedMmwave();
+        mBundle.putBoolean(CarrierConfigManager.KEY_NR_TIMERS_RESET_ON_VOICE_QOS_BOOL,
+                true);
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,30");
+        sendCarrierConfigChanged();
+
+        // should trigger 10 second primary timer
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_UNKNOWN).when(mServiceState).getNrFrequencyRange();
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // Qos changed, but not in call, so no thing happens.
+        ArgumentCaptor<DataNetworkControllerCallback> dataNetworkControllerCallbackCaptor =
+                ArgumentCaptor.forClass(DataNetworkControllerCallback.class);
+        verify(mDataNetworkController).registerDataNetworkControllerCallback(
+                dataNetworkControllerCallbackCaptor.capture());
+        DataNetworkControllerCallback callback = dataNetworkControllerCallbackCaptor.getValue();
+        callback.onQosSessionsChanged(List.of(
+                new QosBearerSession(1, new EpsQos(
+                        new Qos.QosBandwidth(1000, 1),
+                        new Qos.QosBandwidth(1000, 0),
+                        9 /* QCI */), Collections.emptyList())));
+        processAllMessages();
+
+        // Confirm if QCI not 1, timers are still active.
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // Send Voice QOS where QCI is 1, confirm timers are cancelled.
+        callback.onQosSessionsChanged(List.of(
+                new QosBearerSession(1, new EpsQos(
+                        new Qos.QosBandwidth(1000, 1),
+                        new Qos.QosBandwidth(1000, 0),
+                        1 /* QCI */), Collections.emptyList())));
+        processAllMessages();
+
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
+    }
+
     private void setPhysicalLinkStatus(boolean state) {
         List<PhysicalChannelConfig> lastPhysicalChannelConfigList = new ArrayList<>();
         // If PhysicalChannelConfigList is empty, PhysicalLinkStatus is
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
index 90105e3..6743d1c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
@@ -38,6 +38,7 @@
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
+import android.telephony.ModemInfo;
 import android.telephony.PhoneCapability;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyRegistryManager;
@@ -55,8 +56,12 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -69,10 +74,24 @@
     PhoneConfigurationManager.MockableInterface mMi;
 
     private static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 1;
+    private static final PhoneCapability STATIC_DSDA_CAPABILITY;
     PhoneConfigurationManager mPcm;
     private FeatureFlags mFeatureFlags;
     private TelephonyRegistryManager mMockRegistryManager;
 
+    static {
+        ModemInfo modemInfo1 = new ModemInfo(0, 0, true, true);
+        ModemInfo modemInfo2 = new ModemInfo(1, 0, true, true);
+
+        List<ModemInfo> logicalModemList = new ArrayList<>();
+        logicalModemList.add(modemInfo1);
+        logicalModemList.add(modemInfo2);
+        int[] deviceNrCapabilities = new int[0];
+
+        STATIC_DSDA_CAPABILITY = new PhoneCapability(2, 1, logicalModemList, false,
+                deviceNrCapabilities);
+    }
+
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
@@ -85,7 +104,7 @@
         mPhone.mCi = mMockCi0;
         mCT.mCi = mMockCi0;
         mPhone1.mCi = mMockCi1;
-        doReturn(RIL.RADIO_HAL_VERSION_2_1).when(mMockRadioConfigProxy).getVersion();
+        doReturn(RIL.RADIO_HAL_VERSION_2_2).when(mMockRadioConfigProxy).getVersion();
         mMockRegistryManager = mContext.getSystemService(TelephonyRegistryManager.class);
     }
 
@@ -138,15 +157,7 @@
         init(1);
         assertEquals(PhoneCapability.DEFAULT_SSSS_CAPABILITY, mPcm.getStaticPhoneCapability());
 
-        ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
-        verify(mMockRadioConfig).getPhoneCapability(captor.capture());
-        Message msg = captor.getValue();
-        AsyncResult.forMessage(msg, PhoneCapability.DEFAULT_DSDS_CAPABILITY, null);
-        msg.sendToTarget();
-        processAllMessages();
-
-        // Not static capability should indicate DSDS capable.
-        assertEquals(PhoneCapability.DEFAULT_DSDS_CAPABILITY, mPcm.getStaticPhoneCapability());
+        setAndVerifyStaticCapability(PhoneCapability.DEFAULT_DSDS_CAPABILITY);
     }
 
     @Test
@@ -211,12 +222,77 @@
         assertTrue(mPcm.getSlotsSupportingSimultaneousCellularCalls().isEmpty());
     }
 
+    /**
+     * If the device uses the older "dsda" multi_sim_config setting, ensure that DSDA is set
+     * statically for that device and subId updates work.
+     */
+    @Test
+    @SmallTest
+    public void testBkwdsCompatSimultaneousCallingDsda() throws Exception {
+        doReturn(true).when(mFeatureFlags).simultaneousCallingIndications();
+        doReturn(RIL.RADIO_HAL_VERSION_2_1).when(mMockRadioConfigProxy).getVersion();
+        doReturn(Optional.of("dsda")).when(mMi).getMultiSimProperty();
+        final int phone0SubId = 2;
+        final int phone1SubId = 3;
+        mPhones = new Phone[]{mPhone, mPhone1};
+        doReturn(0).when(mPhone).getPhoneId();
+        doReturn(1).when(mPhone1).getPhoneId();
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+        init(2);
+        doReturn(phone0SubId).when(mPhone).getSubId();
+        doReturn(phone1SubId).when(mPhone1).getSubId();
+        Set<Integer>[] cachedSimultaneousCallingSlots = new Set[]{Collections.emptySet()};
+        mPcm.registerForSimultaneousCellularCallingSlotsChanged(newSlots ->
+                cachedSimultaneousCallingSlots[0] = newSlots);
+
+        mPcm.getStaticPhoneCapability();
+        setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
+        ArgumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener> cBCaptor =
+                ArgumentCaptor.forClass(SubscriptionManager.OnSubscriptionsChangedListener.class);
+        verify(mMockRegistryManager).addOnSubscriptionsChangedListener(cBCaptor.capture(), any());
+        processAllMessages();
+
+        int[] enabledLogicalSlots = {0, 1};
+        HashSet<Integer> expectedSlots = new HashSet<>(2);
+        for (int i : enabledLogicalSlots) {
+            expectedSlots.add(i);
+        }
+        HashSet<Integer> expectedSubIds = new HashSet<>(2);
+        expectedSubIds.add(phone0SubId);
+        expectedSubIds.add(phone1SubId);
+        assertEquals(expectedSlots, mPcm.getSlotsSupportingSimultaneousCellularCalls());
+        verify(mMockRegistryManager).notifySimultaneousCellularCallingSubscriptionsChanged(
+                eq(expectedSubIds));
+        assertEquals(expectedSlots, cachedSimultaneousCallingSlots[0]);
+
+        // Change sub ID mapping
+        final int phone1SubIdV2 = 4;
+        expectedSubIds.clear();
+        expectedSubIds.add(phone0SubId);
+        expectedSubIds.add(phone1SubIdV2);
+        doReturn(phone1SubIdV2).when(mPhone1).getSubId();
+        cBCaptor.getValue().onSubscriptionsChanged();
+        processAllMessages();
+        verify(mMockRegistryManager, times(2))
+                .notifySimultaneousCellularCallingSubscriptionsChanged(eq(expectedSubIds));
+    }
+
     @Test
     @SmallTest
     public void testUpdateSimultaneousCallingSupportNotifications() throws Exception {
         // retry simultaneous calling tests, but with notifications enabled this time
         doReturn(true).when(mFeatureFlags).simultaneousCallingIndications();
+
+        final int phone0SubId = 2;
+        final int phone1SubId = 3;
+        mPhones = new Phone[]{mPhone, mPhone1};
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
         init(2);
+        doReturn(phone0SubId).when(mPhone).getSubId();
+        doReturn(phone1SubId).when(mPhone1).getSubId();
+        Set<Integer>[] cachedSimultaneousCallingSlots = new Set[]{Collections.emptySet()};
+        mPcm.registerForSimultaneousCellularCallingSlotsChanged(newSlots ->
+                cachedSimultaneousCallingSlots[0] = newSlots);
 
         // Simultaneous calling enabled
         mPcm.updateSimultaneousCallingSupport();
@@ -228,13 +304,17 @@
         msg.sendToTarget();
         processAllMessages();
 
-        HashSet<Integer> expectedSlots = new HashSet<>();
+        HashSet<Integer> expectedSlots = new HashSet<>(2);
         for (int i : enabledLogicalSlots) {
             expectedSlots.add(i);
         }
+        HashSet<Integer> expectedSubIds = new HashSet<>(2);
+        expectedSubIds.add(phone0SubId);
+        expectedSubIds.add(phone1SubId);
         assertEquals(expectedSlots, mPcm.getSlotsSupportingSimultaneousCellularCalls());
         verify(mMockRegistryManager).notifySimultaneousCellularCallingSubscriptionsChanged(
-                eq(expectedSlots));
+                eq(expectedSubIds));
+        assertEquals(expectedSlots, cachedSimultaneousCallingSlots[0]);
 
         // Simultaneous Calling Disabled
         mPcm.updateSimultaneousCallingSupport();
@@ -249,6 +329,59 @@
         assertEquals(Collections.emptySet(), mPcm.getSlotsSupportingSimultaneousCellularCalls());
         verify(mMockRegistryManager, times(2))
                 .notifySimultaneousCellularCallingSubscriptionsChanged(eq(Collections.emptySet()));
+        assertEquals(Collections.emptySet(), cachedSimultaneousCallingSlots[0]);
+    }
+
+    @Test
+    @SmallTest
+    public void testSimultaneousCallingSubIdMappingChanges() throws Exception {
+        doReturn(true).when(mFeatureFlags).simultaneousCallingIndications();
+        final int phone0SubId = 2;
+        final int phone1SubId = 3;
+        mPhones = new Phone[]{mPhone, mPhone1};
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+        init(2);
+        doReturn(phone0SubId).when(mPhone).getSubId();
+        doReturn(phone1SubId).when(mPhone1).getSubId();
+
+        // Set the capability to DSDA mode to register listener, which will also trigger
+        // simultaneous calling evaluation
+        mPcm.getCurrentPhoneCapability();
+        setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
+        ArgumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener> cBCaptor =
+                ArgumentCaptor.forClass(SubscriptionManager.OnSubscriptionsChangedListener.class);
+        verify(mMockRegistryManager).addOnSubscriptionsChangedListener(cBCaptor.capture(), any());
+
+        int[] enabledLogicalSlots = {0, 1};
+        ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockRadioConfig).updateSimultaneousCallingSupport(captor.capture());
+        Message msg = captor.getValue();
+        // Simultaneous calling enabled
+        AsyncResult.forMessage(msg, enabledLogicalSlots, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        HashSet<Integer> expectedSlots = new HashSet<>(2);
+        for (int i : enabledLogicalSlots) {
+            expectedSlots.add(i);
+        }
+        HashSet<Integer> expectedSubIds = new HashSet<>(2);
+        expectedSubIds.add(phone0SubId);
+        expectedSubIds.add(phone1SubId);
+        assertEquals(expectedSlots, mPcm.getSlotsSupportingSimultaneousCellularCalls());
+        verify(mMockRegistryManager).notifySimultaneousCellularCallingSubscriptionsChanged(
+                eq(expectedSubIds));
+
+        // Change sub ID mapping
+        final int phone1SubIdV2 = 4;
+        expectedSubIds.clear();
+        expectedSubIds.add(phone0SubId);
+        expectedSubIds.add(phone1SubIdV2);
+        doReturn(phone1SubIdV2).when(mPhone1).getSubId();
+        cBCaptor.getValue().onSubscriptionsChanged();
+        processAllMessages();
+        verify(mMockRegistryManager, times(2))
+                .notifySimultaneousCellularCallingSubscriptionsChanged(eq(expectedSubIds));
     }
 
     @Test
@@ -429,4 +562,15 @@
         verify(mMockCi1, times(1)).registerForAvailable(any(), anyInt(), any());
         verify(mMockCi1, times(1)).onSlotActiveStatusChange(anyBoolean());
     }
+
+    private void setAndVerifyStaticCapability(PhoneCapability capability) {
+        ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockRadioConfig).getPhoneCapability(captor.capture());
+        Message msg = captor.getValue();
+        AsyncResult.forMessage(msg, capability, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        assertEquals(capability, mPcm.getStaticPhoneCapability());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
index 4afdfe9..ac92b8f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
@@ -68,6 +68,7 @@
                 .setOnlyNonTerrestrialNetwork(true)
                 .setServiceCapabilities(SubscriptionManager.getServiceCapabilitiesSet(
                     SubscriptionManager.SERVICE_CAPABILITY_DATA_BITMASK))
+                .setTransferStatus(1)
                 .build();
     }
 
@@ -92,6 +93,9 @@
         }
         assertThat(mSubscriptionInfoUT.getServiceCapabilities()).isEqualTo(
                 Set.of(SubscriptionManager.SERVICE_CAPABILITY_DATA));
+        if (Flags.supportPsimToEsimConversion()) {
+            assertThat(mSubscriptionInfoUT.getTransferStatus()).isEqualTo(1);
+        }
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index b515372..a6e06e3 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -125,6 +125,7 @@
 import com.android.internal.telephony.metrics.VoiceCallSessionStats;
 import com.android.internal.telephony.satellite.SatelliteController;
 import com.android.internal.telephony.security.CellularIdentifierDisclosureNotifier;
+import com.android.internal.telephony.security.CellularNetworkSecuritySafetySource;
 import com.android.internal.telephony.security.NullCipherNotifier;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.test.SimulatedCommands;
@@ -285,6 +286,7 @@
     protected ServiceStateStats mServiceStateStats;
     protected SatelliteController mSatelliteController;
     protected DeviceStateHelper mDeviceStateHelper;
+    protected CellularNetworkSecuritySafetySource mSafetySource;
     protected CellularIdentifierDisclosureNotifier mIdentifierDisclosureNotifier;
     protected DomainSelectionResolver mDomainSelectionResolver;
     protected NullCipherNotifier mNullCipherNotifier;
@@ -422,6 +424,16 @@
         field.set(obj, newValue);
     }
 
+    protected static <T> T getPrivateField(Object object, String fieldName, Class<T> fieldType)
+            throws Exception {
+
+        Class<?> clazz = object.getClass();
+        Field field = clazz.getDeclaredField(fieldName);
+        field.setAccessible(true);
+
+        return fieldType.cast(field.get(object));
+    }
+
     protected synchronized void restoreInstance(final Class c, final String instanceName,
                                                 final Object obj) throws Exception {
         InstanceKey key = new InstanceKey(c, instanceName, obj);
@@ -560,6 +572,7 @@
         mServiceStateStats = Mockito.mock(ServiceStateStats.class);
         mSatelliteController = Mockito.mock(SatelliteController.class);
         mDeviceStateHelper = Mockito.mock(DeviceStateHelper.class);
+        mSafetySource = Mockito.mock(CellularNetworkSecuritySafetySource.class);
         mIdentifierDisclosureNotifier = Mockito.mock(CellularIdentifierDisclosureNotifier.class);
         mDomainSelectionResolver = Mockito.mock(DomainSelectionResolver.class);
         mNullCipherNotifier = Mockito.mock(NullCipherNotifier.class);
@@ -635,7 +648,7 @@
                         nullable(IccCardStatus.class), anyInt(), nullable(UiccCard.class),
                         nullable(Object.class), any(FeatureFlags.class));
         doReturn(mCT).when(mTelephonyComponentFactory)
-                .makeGsmCdmaCallTracker(nullable(GsmCdmaPhone.class));
+                .makeGsmCdmaCallTracker(nullable(GsmCdmaPhone.class), any(FeatureFlags.class));
         doReturn(mIccPhoneBookIntManager).when(mTelephonyComponentFactory)
                 .makeIccPhoneBookInterfaceManager(nullable(Phone.class));
         doReturn(mDisplayInfoController).when(mTelephonyComponentFactory)
@@ -676,9 +689,11 @@
                         any(DataServiceManager.class), any(Looper.class),
                         any(FeatureFlags.class),
                         any(DataProfileManager.DataProfileManagerCallback.class));
+        doReturn(mSafetySource).when(mTelephonyComponentFactory)
+                .makeCellularNetworkSecuritySafetySource(any(Context.class));
         doReturn(mIdentifierDisclosureNotifier)
                 .when(mTelephonyComponentFactory)
-                .makeIdentifierDisclosureNotifier();
+                .makeIdentifierDisclosureNotifier(any(CellularNetworkSecuritySafetySource.class));
         doReturn(mNullCipherNotifier)
                 .when(mTelephonyComponentFactory)
                 .makeNullCipherNotifier();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/ApnSettingTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/ApnSettingTest.java
index 378df4b..0c7342e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/ApnSettingTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/ApnSettingTest.java
@@ -28,6 +28,7 @@
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.PersistableBundle;
+import android.provider.Telephony;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
@@ -42,6 +43,7 @@
 import java.lang.reflect.Modifier;
 import java.net.InetAddress;
 import java.util.List;
+import java.util.Set;
 
 public class ApnSettingTest extends TelephonyTest {
 
@@ -206,12 +208,16 @@
         final String[] dummyStringArr = new String[] {"dummy"};
         final InetAddress dummyProxyAddress = InetAddress.getByAddress(new byte[]{0, 0, 0, 0});
         final Uri dummyUri = Uri.parse("www.google.com");
+
+        final Set<String> excludedFields = Set.of("mEditedStatus");
+
         // base apn
         ApnSetting baseApn = createApnSetting(ApnSetting.TYPE_MMS | ApnSetting.TYPE_DEFAULT);
         Field[] fields = ApnSetting.class.getDeclaredFields();
         for (Field f : fields) {
             int modifiers = f.getModifiers();
-            if (Modifier.isStatic(modifiers) || !Modifier.isFinal(modifiers)) {
+            if (Modifier.isStatic(modifiers) || !Modifier.isFinal(modifiers)
+                    || excludedFields.contains(f.getName())) {
                 continue;
             }
             f.setAccessible(true);
@@ -414,4 +420,33 @@
         // InfrastructureBitmask value set to '1(cellular)'
         assertEquals(infrastructureBitmask, apn2.getInfrastructureBitmask());
     }
+
+    @Test
+    public void testEditedStatus() {
+        ApnSetting apn = new ApnSetting.Builder()
+                .setId(1234)
+                .setOperatorNumeric("310260")
+                .setEntryName("mms")
+                .setApnName("mms")
+                .setApnTypeBitmask(ApnSetting.TYPE_MMS | ApnSetting.TYPE_DEFAULT)
+                .setProtocol(ApnSetting.PROTOCOL_IPV4V6)
+                .setNetworkTypeBitmask((int) (TelephonyManager.NETWORK_TYPE_BITMASK_LTE))
+                .setEditedStatus(Telephony.Carriers.USER_EDITED)
+                .build();
+        assertEquals(Telephony.Carriers.USER_EDITED, apn.getEditedStatus());
+
+        ApnSetting apn2 = new ApnSetting.Builder()
+                .setId(1234)
+                .setOperatorNumeric("310260")
+                .setEntryName("mms")
+                .setApnName("mms")
+                .setApnTypeBitmask(ApnSetting.TYPE_MMS | ApnSetting.TYPE_DEFAULT)
+                .setProtocol(ApnSetting.PROTOCOL_IPV4V6)
+                .setNetworkTypeBitmask((int) (TelephonyManager.NETWORK_TYPE_BITMASK_LTE))
+                .setEditedStatus(Telephony.Carriers.CARRIER_EDITED)
+                .build();
+
+        // The edited status should not affect equals
+        assertEquals(apn, apn2);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java
index 5b91aee..50ee5c8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java
@@ -20,10 +20,15 @@
 
 import static com.android.internal.telephony.data.AutoDataSwitchController.EVALUATION_REASON_DATA_SETTINGS_CHANGED;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
@@ -32,6 +37,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.app.AlarmManager;
 import android.app.NotificationManager;
 import android.content.Context;
 import android.net.NetworkCapabilities;
@@ -59,6 +65,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Map;
+
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class AutoDataSwitchControllerTest extends TelephonyTest {
@@ -66,7 +74,7 @@
     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 EVENT_STABILITY_CHECK_PASSED = 5;
 
     private static final int PHONE_1 = 0;
     private static final int SUB_1 = 1;
@@ -79,12 +87,14 @@
     private boolean mIsNonTerrestrialNetwork = false;
     // Mocked
     private AutoDataSwitchController.AutoDataSwitchControllerCallback mMockedPhoneSwitcherCallback;
+    private AlarmManager mMockedAlarmManager;
 
     // Real
     private TelephonyDisplayInfo mGoodTelephonyDisplayInfo;
     private TelephonyDisplayInfo mBadTelephonyDisplayInfo;
     private int mDefaultDataSub;
     private AutoDataSwitchController mAutoDataSwitchControllerUT;
+    private Map<Integer, AlarmManager.OnAlarmListener> mEventsToAlarmListener;
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
@@ -94,6 +104,7 @@
                 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false /*roaming*/);
         mMockedPhoneSwitcherCallback =
                 mock(AutoDataSwitchController.AutoDataSwitchControllerCallback.class);
+        mMockedAlarmManager = mock(AlarmManager.class);
 
         doReturn(PHONE_1).when(mPhone).getPhoneId();
         doReturn(SUB_1).when(mPhone).getSubId();
@@ -147,6 +158,8 @@
         doReturn(true).when(mDataConfigManager).isPingTestBeforeAutoDataSwitchRequired();
         doReturn(10000L).when(mDataConfigManager)
                 .getAutoDataSwitchAvailabilityStabilityTimeThreshold();
+        doReturn(120000L).when(mDataConfigManager)
+                .getAutoDataSwitchPerformanceStabilityTimeThreshold();
         doReturn(MAX_RETRY).when(mDataConfigManager).getAutoDataSwitchValidationMaxRetry();
         doReturn(SCORE_TOLERANCE).when(mDataConfigManager).getAutoDataSwitchScoreTolerance();
         doAnswer(invocation -> {
@@ -166,6 +179,11 @@
         mAutoDataSwitchControllerUT = new AutoDataSwitchController(mContext, Looper.myLooper(),
                 mPhoneSwitcher, mFeatureFlags, mMockedPhoneSwitcherCallback);
 
+        replaceInstance(AutoDataSwitchController.class, "mAlarmManager",
+                mAutoDataSwitchControllerUT, mMockedAlarmManager);
+        mEventsToAlarmListener = getPrivateField(mAutoDataSwitchControllerUT,
+                "mEventsToAlarmListener", Map.class);
+
         doReturn(true).when(mFeatureFlags).autoSwitchAllowRoaming();
         doReturn(true).when(mFeatureFlags).carrierEnabledSatelliteFlag();
     }
@@ -356,6 +374,7 @@
 
     @Test
     public void testCancelSwitch_onPrimary_rat_signalStrength() {
+        doReturn(true).when(mFeatureFlags).autoDataSwitchRatSs();
         // 4.1.1 Display info and signal strength on secondary phone became bad,
         // but primary is still OOS, so still switch to the secondary.
         prepareIdealUsesNonDdsCondition();
@@ -371,30 +390,39 @@
         // but primary become service, then don't switch.
         prepareIdealUsesNonDdsCondition();
         processAllFutureMessages();
-        clearInvocations(mMockedPhoneSwitcherCallback);
+        clearInvocations(mMockedPhoneSwitcherCallback, mMockedAlarmManager);
         serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         displayInfoChanged(PHONE_2, mBadTelephonyDisplayInfo);
         signalStrengthChanged(PHONE_2, SignalStrength.SIGNAL_STRENGTH_MODERATE);
         processAllFutureMessages();
-        verify(mMockedPhoneSwitcherCallback).onRequireCancelAnyPendingAutoSwitchValidation();
+        verify(mMockedPhoneSwitcherCallback, atLeastOnce())
+                .onRequireCancelAnyPendingAutoSwitchValidation();
+        verify(mMockedAlarmManager, atLeastOnce()).cancel(mEventsToAlarmListener.get(
+                EVENT_STABILITY_CHECK_PASSED));
 
         // 4.2 Display info on default phone became good just as the secondary
         prepareIdealUsesNonDdsCondition();
         processAllFutureMessages();
-        clearInvocations(mMockedPhoneSwitcherCallback);
+        clearInvocations(mMockedPhoneSwitcherCallback, mMockedAlarmManager);
         serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         displayInfoChanged(PHONE_1, mGoodTelephonyDisplayInfo);
         processAllFutureMessages();
-        verify(mMockedPhoneSwitcherCallback).onRequireCancelAnyPendingAutoSwitchValidation();
+        verify(mMockedPhoneSwitcherCallback, atLeastOnce())
+                .onRequireCancelAnyPendingAutoSwitchValidation();
+        verify(mMockedAlarmManager, atLeastOnce()).cancel(mEventsToAlarmListener.get(
+                EVENT_STABILITY_CHECK_PASSED));
 
         // 4.3 Signal strength on default phone became just as good as the secondary
         prepareIdealUsesNonDdsCondition();
         processAllFutureMessages();
-        clearInvocations(mMockedPhoneSwitcherCallback);
+        clearInvocations(mMockedPhoneSwitcherCallback, mMockedAlarmManager);
         serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         signalStrengthChanged(PHONE_1, SignalStrength.SIGNAL_STRENGTH_GREAT);
         processAllFutureMessages();
-        verify(mMockedPhoneSwitcherCallback).onRequireCancelAnyPendingAutoSwitchValidation();
+        verify(mMockedPhoneSwitcherCallback, atLeastOnce())
+                .onRequireCancelAnyPendingAutoSwitchValidation();
+        verify(mMockedAlarmManager, atLeastOnce()).cancel(mEventsToAlarmListener.get(
+                EVENT_STABILITY_CHECK_PASSED));
     }
 
     @Test
@@ -459,16 +487,18 @@
     @Test
     public void testOnNonDdsSwitchBackToPrimary_rat_signalStrength() {
         doReturn(true).when(mFeatureFlags).autoDataSwitchRatSs();
+        prepareIdealUsesNonDdsCondition();
+        processAllFutureMessages();
         doReturn(PHONE_2).when(mPhoneSwitcher).getPreferredDataPhoneId();
 
-        prepareIdealUsesNonDdsCondition();
         // 4.1 Display info and signal strength on secondary phone became bad just as the default
-        // Expect no switch since both phone has the same score.
+        // Expect switch back since both phone has the same score.
         serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         displayInfoChanged(PHONE_2, mBadTelephonyDisplayInfo);
         signalStrengthChanged(PHONE_2, SignalStrength.SIGNAL_STRENGTH_POOR);
         processAllFutureMessages();
-        verify(mMockedPhoneSwitcherCallback, never()).onRequireValidation(anyInt(), anyBoolean());
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
+                true/*needValidation*/);
 
         clearInvocations(mMockedPhoneSwitcherCallback);
         prepareIdealUsesNonDdsCondition();
@@ -504,7 +534,7 @@
     }
 
     @Test
-    public void testStabilityCheckOverride() {
+    public void testStabilityCheckOverride_basic() {
         // Starting stability check for switching to non-DDS
         prepareIdealUsesNonDdsCondition();
         processAllMessages();
@@ -515,9 +545,6 @@
         // Display info and signal strength on secondary phone became worse than the default.
         // Expect to switch back, and it should override the previous stability check
         serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
-        signalStrengthChanged(PHONE_1, SignalStrength.SIGNAL_STRENGTH_GREAT);
-        displayInfoChanged(PHONE_2, mBadTelephonyDisplayInfo);
-        signalStrengthChanged(PHONE_2, SignalStrength.SIGNAL_STRENGTH_POOR);
         // process all messages include the delayed message
         processAllFutureMessages();
 
@@ -528,6 +555,25 @@
     }
 
     @Test
+    public void testStabilityCheckOverride_uses_rat_signalStrength() {
+        doReturn(true).when(mFeatureFlags).autoDataSwitchRatSs();
+        // Switching due to availability first.
+        prepareIdealUsesNonDdsCondition();
+
+        // Verify stability check pending with short timer.
+        verify(mMockedPhoneSwitcherCallback, never()).onRequireValidation(anyInt(), anyBoolean());
+
+        // Switching due to performance now, should override to use long timer.
+        serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+
+        // Verify stability check pending with long timer.
+        assertThat(mAutoDataSwitchControllerUT.hasMessages(EVENT_STABILITY_CHECK_PASSED)).isFalse();
+        verify(mMockedAlarmManager).setExact(anyInt(), anyLong(), anyString(),
+                eq(mEventsToAlarmListener.get(
+                        EVENT_STABILITY_CHECK_PASSED)), any());
+    }
+
+    @Test
     public void testValidationFailedRetry() {
         prepareIdealUsesNonDdsCondition();
 
@@ -643,6 +689,7 @@
     @Test
     public void testRatSignalStrengthSkipEvaluation() {
         // Verify the secondary phone is OOS and its score(0) is too low to justify the evaluation
+        clearInvocations(mMockedPhoneSwitcherCallback);
         displayInfoChanged(PHONE_2, mBadTelephonyDisplayInfo);
         processAllFutureMessages();
         verify(mMockedPhoneSwitcherCallback, never())
@@ -726,9 +773,19 @@
         Message msg = mAutoDataSwitchControllerUT.obtainMessage(EVENT_SERVICE_STATE_CHANGED);
         msg.obj = new AsyncResult(phoneId, null, null);
         mAutoDataSwitchControllerUT.sendMessage(msg);
+        processAllMessages();
     }
     private void setDefaultDataSubId(int defaultDataSub) {
         mDefaultDataSub = defaultDataSub;
         doReturn(mDefaultDataSub).when(mSubscriptionManagerService).getDefaultDataSubId();
     }
+
+    @Override
+    public void processAllFutureMessages() {
+        if (mFeatureFlags.autoDataSwitchRatSs()
+                && mEventsToAlarmListener.containsKey(EVENT_STABILITY_CHECK_PASSED)) {
+            mEventsToAlarmListener.get(EVENT_STABILITY_CHECK_PASSED).onAlarm();
+        }
+        super.processAllFutureMessages();
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataEvaluationTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataEvaluationTest.java
index 6d4c0c1..8ce0894 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataEvaluationTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataEvaluationTest.java
@@ -58,6 +58,14 @@
         mDataEvaluationUT.removeDataDisallowedReason(
                 DataEvaluation.DataDisallowedReason.DATA_DISABLED);
         assertThat(mDataEvaluationUT.getDataDisallowedReasons().size()).isEqualTo(1);
+
+        mDataEvaluationUT.addDataDisallowedReason(
+                DataEvaluation.DataDisallowedReason.DATA_LIMIT_REACHED);
+        assertThat(mDataEvaluationUT.getDataDisallowedReasons().size()).isEqualTo(2);
+
+        mDataEvaluationUT.removeDataDisallowedReason(
+                DataEvaluation.DataDisallowedReason.ROAMING_DISABLED);
+        assertThat(mDataEvaluationUT.getDataDisallowedReasons().size()).isEqualTo(1);
     }
 
 
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 14bad95..00e6680 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
@@ -648,8 +648,26 @@
     private void serviceStateChanged(@NetworkType int networkType,
             @RegistrationState int dataRegState, @RegistrationState int voiceRegState,
             @RegistrationState int iwlanRegState, DataSpecificRegistrationInfo dsri) {
+        boolean isEmergencyOnly = false;
+        if (dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_DENIED) {
+            isEmergencyOnly = true;
+        }
         ServiceState ss = createSS(networkType, networkType, dataRegState, voiceRegState,
-                iwlanRegState, dsri);
+                iwlanRegState, dsri, isEmergencyOnly);
+
+        doReturn(ss).when(mSST).getServiceState();
+        doReturn(ss).when(mPhone).getServiceState();
+
+        mDataNetworkControllerUT.obtainMessage(17/*EVENT_SERVICE_STATE_CHANGED*/).sendToTarget();
+        processAllMessages();
+    }
+
+    private void serviceStateChanged(@NetworkType int networkType,
+            @RegistrationState int dataRegState, @RegistrationState int voiceRegState,
+            @RegistrationState int iwlanRegState, DataSpecificRegistrationInfo dsri,
+            boolean isEmergencyOnly) {
+        ServiceState ss = createSS(networkType, networkType, dataRegState, voiceRegState,
+                iwlanRegState, dsri, isEmergencyOnly);
 
         doReturn(ss).when(mSST).getServiceState();
         doReturn(ss).when(mPhone).getServiceState();
@@ -661,7 +679,8 @@
     private ServiceState createSS(@NetworkType int dataNetworkType,
             @NetworkType int voiceNetworkType,
             @RegistrationState int dataRegState, @RegistrationState int voiceRegState,
-            @RegistrationState int iwlanRegState, DataSpecificRegistrationInfo dsri) {
+            @RegistrationState int iwlanRegState, DataSpecificRegistrationInfo dsri,
+            boolean isEmergencyOnly) {
         if (dsri == null) {
             dsri = new DataSpecificRegistrationInfo.Builder(8)
                     .setNrAvailable(true)
@@ -682,6 +701,7 @@
                 .setDataSpecificInfo(dsri)
                 .setIsNonTerrestrialNetwork(mIsNonTerrestrialNetwork)
                 .setAvailableServices(mCarrierSupportedSatelliteServices)
+                .setEmergencyOnly(isEmergencyOnly)
                 .build());
 
         ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
@@ -691,6 +711,7 @@
                 .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
                 .setIsNonTerrestrialNetwork(mIsNonTerrestrialNetwork)
                 .setAvailableServices(mCarrierSupportedSatelliteServices)
+                .setEmergencyOnly(isEmergencyOnly)
                 .build());
 
         ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
@@ -698,6 +719,7 @@
                 .setAccessNetworkTechnology(voiceNetworkType)
                 .setRegistrationState(voiceRegState)
                 .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
+                .setEmergencyOnly(isEmergencyOnly)
                 .build());
 
         ss.setDataRoamingFromRegistration(dataRegState
@@ -808,6 +830,12 @@
         mCarrierConfig.putIntArray(CarrierConfigManager
                         .KEY_CAPABILITIES_EXEMPT_FROM_SINGLE_DC_CHECK_INT_ARRAY,
                 new int[]{NetworkCapabilities.NET_CAPABILITY_IMS});
+        mCarrierConfig.putBooleanArray(
+                CarrierConfigManager.KEY_DATA_STALL_RECOVERY_SHOULD_SKIP_BOOL_ARRAY,
+                new boolean[] {false, false, true, false, false}
+        );
+        mCarrierConfig.putLongArray(CarrierConfigManager.KEY_DATA_STALL_RECOVERY_TIMERS_LONG_ARRAY,
+                new long[] {180000, 180000, 180000, 180000});
 
         mCarrierConfig.putLongArray(CarrierConfigManager.KEY_DATA_STALL_RECOVERY_TIMERS_LONG_ARRAY,
                 new long[] {100, 100, 100, 100});
@@ -818,6 +846,8 @@
         mContextFixture.putResource(com.android.internal.R.string.config_bandwidthEstimateSource,
                 "bandwidth_estimator");
 
+        mContextFixture.putBooleanResource(com.android.internal.R.bool
+                .config_honor_data_retry_timer_for_emergency_network, true);
         mContextFixture.putIntResource(com.android.internal.R.integer
                         .config_delay_for_ims_dereg_millis, 3000);
         mContextFixture.putBooleanResource(com.android.internal.R.bool
@@ -2125,6 +2155,53 @@
     }
 
     @Test
+    public void testEmergencyRequestWithThrottling() throws Exception {
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, /* data */
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, /* voice */
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, /* iwlan */
+                null, false);
+        mDataNetworkControllerUT.getDataRetryManager()
+                .registerCallback(mMockedDataRetryManagerCallback);
+
+        setFailedSetupDataResponse(mMockedWwanDataServiceManager, DataFailCause.PROTOCOL_ERRORS,
+                10000, false);
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_EIMS));
+        processAllMessages();
+
+        // There should be only one attempt, and no retry should happen because the second one
+        // was throttled.
+        verify(mMockedWwanDataServiceManager, times(1)).setupDataCall(anyInt(),
+                any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(),
+                any(), any(), anyBoolean(), any(Message.class));
+
+        ArgumentCaptor<List<ThrottleStatus>> throttleStatusCaptor =
+                ArgumentCaptor.forClass(List.class);
+        verify(mMockedDataRetryManagerCallback)
+                .onThrottleStatusChanged(throttleStatusCaptor.capture());
+        assertThat(throttleStatusCaptor.getValue()).hasSize(1);
+        ThrottleStatus throttleStatus = throttleStatusCaptor.getValue().get(0);
+        assertThat(throttleStatus.getApnType()).isEqualTo(ApnSetting.TYPE_EMERGENCY);
+        assertThat(throttleStatus.getRetryType())
+                .isEqualTo(ThrottleStatus.RETRY_TYPE_NEW_CONNECTION);
+        assertThat(throttleStatus.getTransportType())
+                .isEqualTo(AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        Mockito.reset(mMockedWwanDataServiceManager);
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_DENIED, /* data */
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, /* voice */
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, /* iwlan */
+                null, true);
+
+        // No retry should happen because the second one was throttled.
+        verify(mMockedWwanDataServiceManager, never()).setupDataCall(anyInt(),
+                any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(),
+                any(), any(), anyBoolean(), any(Message.class));
+    }
+
+    @Test
     public void testHandoverRuleFromString() {
         HandoverRule handoverRule = new HandoverRule("source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, "
                 + "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed");
@@ -4681,7 +4758,7 @@
                 TelephonyManager.NETWORK_TYPE_1xRTT /* voice RAT */,
                 NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING ,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME,
-                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, null);
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, null, true);
         doReturn(ss).when(mSST).getServiceState();
         mDataNetworkControllerUT.obtainMessage(EVENT_SERVICE_STATE_CHANGED).sendToTarget();
         mDataNetworkControllerUT.removeNetworkRequest(request);
@@ -4842,6 +4919,9 @@
     @Test
     public void testNetworkOnProvisioningProfileClass_WithFlagEnabled() throws Exception {
         when(mFeatureFlags.esimBootstrapProvisioningFlag()).thenReturn(true);
+        // Allowed data limit Unlimited
+        mContextFixture.putIntResource(com.android.internal.R.integer
+                .config_esim_bootstrap_data_limit_bytes, -1);
         doReturn(new SubscriptionInfoInternal.Builder().setId(1)
                 .setProfileClass(SubscriptionManager.PROFILE_CLASS_PROVISIONING).build())
                 .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
@@ -4869,6 +4949,65 @@
     }
 
     @Test
+    public void testSetUpPdn_WithBootStrapDataLimit_Zero() throws Exception {
+        when(mFeatureFlags.esimBootstrapProvisioningFlag()).thenReturn(true);
+        // Allowed data limit set as zero
+        doReturn(new SubscriptionInfoInternal.Builder().setId(1)
+                .setProfileClass(SubscriptionManager.PROFILE_CLASS_PROVISIONING).build())
+                .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        processAllMessages();
+        // With current consumed bytes is zero, same as allowed limit, data_limit_reached
+        // disallowed reason is met
+        verifyConnectedNetworkHasNoDataProfile(mEsimBootstrapDataProfile);
+
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_IMS,
+                        NetworkCapabilities.NET_CAPABILITY_MMTEL));
+        processAllMessages();
+        // New network request also meets with data limit reached disallowed reason
+        verifyConnectedNetworkHasNoDataProfile(mEsimBootstrapImsProfile);
+    }
+
+    @Test
+    public void testSetUpPdn_WithBootStrapDataLimit_Unlimited() throws Exception {
+        when(mFeatureFlags.esimBootstrapProvisioningFlag()).thenReturn(true);
+        // Allowed data limit
+        mContextFixture.putIntResource(com.android.internal.R.integer
+                 .config_esim_bootstrap_data_limit_bytes, -1/*unlimited*/);
+        doReturn(new SubscriptionInfoInternal.Builder().setId(1)
+                .setProfileClass(SubscriptionManager.PROFILE_CLASS_PROVISIONING).build())
+                .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        processAllMessages();
+        // With allowed data limit unlimited, connection is allowed
+        verifyConnectedNetworkHasDataProfile(mEsimBootstrapDataProfile);
+
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_IMS,
+                        NetworkCapabilities.NET_CAPABILITY_MMTEL));
+        setSuccessfulSetupDataResponse(mMockedDataServiceManagers
+                .get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN), 2);
+        processAllMessages();
+        // With allowed data limit unlimited, connection is allowed
+        verifyConnectedNetworkHasDataProfile(mEsimBootstrapImsProfile);
+
+        // Both internet and IMS should be retained after network re-evaluation
+        mDataNetworkControllerUT.obtainMessage(16 /*EVENT_REEVALUATE_EXISTING_DATA_NETWORKS*/,
+                DataEvaluation.DataEvaluationReason.CHECK_DATA_USAGE).sendToTarget();
+        processAllMessages();
+        // With allowed data limit unlimited, connection is allowed
+        verifyConnectedNetworkHasDataProfile(mEsimBootstrapDataProfile);
+        verifyConnectedNetworkHasDataProfile(mEsimBootstrapImsProfile);
+    }
+
+    @Test
     public void testNetworkOnNonProvisioningProfileClass_WithFlagEnabled() throws Exception {
         when(mFeatureFlags.esimBootstrapProvisioningFlag()).thenReturn(true);
         doReturn(new SubscriptionInfoInternal.Builder().setId(1)
@@ -4894,6 +5033,9 @@
     public void testNtnNetworkOnProvisioningProfileClass_WithFlagEnabled() throws Exception {
         when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
         when(mFeatureFlags.esimBootstrapProvisioningFlag()).thenReturn(true);
+        // Allowed data limit Unlimited
+        mContextFixture.putIntResource(com.android.internal.R.integer
+                .config_esim_bootstrap_data_limit_bytes, -1);
         doReturn(new SubscriptionInfoInternal.Builder().setId(1)
                 .setProfileClass(SubscriptionManager.PROFILE_CLASS_PROVISIONING).build())
                 .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
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 66444da..62fbebc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
@@ -68,6 +68,7 @@
 import android.telephony.data.EpsQos;
 import android.telephony.data.NetworkSliceInfo;
 import android.telephony.data.Qos;
+import android.telephony.data.QosBearerSession;
 import android.telephony.data.TrafficDescriptor;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -2260,4 +2261,44 @@
         assertThat(mDataNetworkUT.getNetworkCapabilities()
                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)).isTrue();
     }
+
+    @Test
+    public void testQosSessionsChanged()  throws Exception {
+        createImsDataNetwork(false/*isMmtel*/);
+        List<QosBearerSession> newQosSessions =
+                List.of(new QosBearerSession(1, mDefaultQos, Collections.emptyList()));
+        DataCallResponse response = new DataCallResponse.Builder()
+                .setCause(0)
+                .setRetryDurationMillis(-1L)
+                .setId(123)
+                .setLinkStatus(DataCallResponse.LINK_STATUS_ACTIVE)
+                .setProtocolType(ApnSetting.PROTOCOL_IPV4V6)
+                .setInterfaceName("ifname")
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(IPV4_ADDRESS), 32),
+                        new LinkAddress(IPV6_ADDRESS + "/64")))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress("10.0.2.3"),
+                        InetAddresses.parseNumericAddress("fd00:976a::9")))
+                .setGatewayAddresses(Arrays.asList(
+                        InetAddresses.parseNumericAddress("10.0.2.15"),
+                        InetAddresses.parseNumericAddress("fe80::2")))
+                .setPcscfAddresses(Arrays.asList(
+                        InetAddresses.parseNumericAddress("fd00:976a:c305:1d::8"),
+                        InetAddresses.parseNumericAddress("fd00:976a:c202:1d::7"),
+                        InetAddresses.parseNumericAddress("fd00:976a:c305:1d::5")))
+                .setMtuV4(1234)
+                .setPduSessionId(1)
+                .setQosBearerSessions(newQosSessions)
+                .setTrafficDescriptors(Collections.emptyList())
+                .setDefaultQos(mDefaultQos)
+                .build();
+
+        // Qos sessions list changed
+        mDataNetworkUT.obtainMessage(8/*EVENT_DATA_STATE_CHANGED*/,
+                new AsyncResult(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                        List.of(response), null)).sendToTarget();
+        processAllMessages();
+
+        verify(mDataNetworkCallback).onQosSessionsChanged(newQosSessions);
+    }
 }
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 e556cb8..44d207d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
@@ -139,7 +139,8 @@
                 Telephony.Carriers.SKIP_464XLAT,
                 Telephony.Carriers.ALWAYS_ON,
                 Telephony.Carriers.INFRASTRUCTURE_BITMASK,
-                Telephony.Carriers.ESIM_BOOTSTRAP_PROVISIONING
+                Telephony.Carriers.ESIM_BOOTSTRAP_PROVISIONING,
+                Telephony.Carriers.EDITED_STATUS
         };
 
         private int mPreferredApnSet = 0;
@@ -180,7 +181,8 @@
                         -1,                     // skip_464xlat
                         0,                      // always_on
                         1,                      // INFRASTRUCTURE_CELLULAR
-                        0                       // esim_bootstrap_provisioning
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 // default internet data profile for RAT CDMA, to test update preferred data profile
                 new Object[]{
@@ -216,8 +218,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_CELLULAR
-                        0                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         2,                      // id
@@ -252,8 +255,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_CELLULAR
-                        0                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         3,                      // id
@@ -288,8 +292,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_CELLULAR
-                        0                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         4,                      // id
@@ -325,8 +330,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_CELLULAR
-                        0                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 // This APN entry is created to test de-duping.
                 new Object[]{
@@ -363,8 +369,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_CELLULAR
-                        0                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         6,                      // id
@@ -400,8 +407,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_CELLULAR
-                        0                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         7,                      // id
@@ -437,8 +445,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_CELLULAR
-                        0                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         8,                      // id
@@ -474,8 +483,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_CELLULAR
-                        0                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         9,                      // id
@@ -512,10 +522,11 @@
                         -1,                     // skip_464xlat
                         0,                      // always_on
                         2,                      // INFRASTRUCTURE_SATELLITE
-                        0                      // esim_bootstrap_provisioning
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
-                        10,                      // id
+                        10,                     // id
                         PLMN,                   // numeric
                         ESIM_BOOTSTRAP_PROVISIONING_APN, // name
                         ESIM_BOOTSTRAP_PROVISIONING_APN, // apn
@@ -527,7 +538,7 @@
                         "",                     // user
                         "",                     // password
                         -1,                     // authtype
-                        "default,supl",          // types
+                        "default,supl",         // types
                         "IPV4V6",               // protocol
                         "IPV4V6",               // roaming_protocol
                         1,                      // carrier_enabled
@@ -548,8 +559,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_CELLULAR
-                        1                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        1,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         11,                      // id
@@ -585,8 +597,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_SATELLITE
-                        1                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_SATELLITE
+                        1,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         12,                     // id
@@ -622,14 +635,15 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        2,                       // INFRASTRUCTURE_SATELLITE
-                        1                       // esim_bootstrap_provisioning
+                        2,                      // INFRASTRUCTURE_SATELLITE
+                        1,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
-                        13,                      // id
+                        13,                     // id
                         PLMN,                   // numeric
-                        RCS_APN1,                // name
-                        RCS_APN1,                // apn
+                        RCS_APN1,               // name
+                        RCS_APN1,               // apn
                         "",                     // proxy
                         "",                     // port
                         "",                     // mmsc
@@ -659,8 +673,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        2,                       // INFRASTRUCTURE_SATELLITE
-                        1                       // esim_bootstrap_provisioning
+                        2,                      // INFRASTRUCTURE_SATELLITE
+                        1,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 }
         );
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java
index cd7aadc..47f8ce2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java
@@ -27,19 +27,23 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Handler;
+import android.os.Message;
 import android.os.RemoteException;
+import android.telecom.PhoneAccount;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.DomainSelectionService;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
 import android.telephony.data.ApnSetting;
 import android.telephony.ims.ImsReasonInfo;
 import android.testing.AndroidTestingRunner;
@@ -171,7 +175,8 @@
         IWwanSelectorResultCallback resultCallback =
                 Mockito.mock(IWwanSelectorResultCallback.class);
 
-        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType, resultCallback);
+        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType,
+                false, resultCallback);
         processAllMessages();
 
         ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
@@ -190,12 +195,13 @@
 
         assertNotNull(handler);
 
-        EmergencyRegResult regResult =
-                new EmergencyRegResult(UTRAN, 0, 0, true, false, 0, 0, "", "", "");
+        EmergencyRegistrationResult regResult =
+                new EmergencyRegistrationResult(UTRAN, 0, 0, true, false, 0, 0, "", "", "");
         handler.sendMessage(handler.obtainMessage(event, new AsyncResult(null, regResult, null)));
         processAllMessages();
 
         verify(resultCallback).onComplete(eq(regResult));
+        verify(mPhone, times(0)).cancelEmergencyNetworkScan(anyBoolean(), any());
     }
 
     @Test
@@ -221,8 +227,8 @@
 
         assertNotNull(wwanCallback);
 
-        wwanCallback.onRequestEmergencyNetworkScan(new int[] { },
-                SCAN_TYPE_NO_PREFERENCE, Mockito.mock(IWwanSelectorResultCallback.class));
+        wwanCallback.onRequestEmergencyNetworkScan(new int[] { }, SCAN_TYPE_NO_PREFERENCE,
+                false, Mockito.mock(IWwanSelectorResultCallback.class));
         processAllMessages();
 
         verify(mPhone).registerForEmergencyNetworkScan(any(), anyInt(), any());
@@ -235,6 +241,196 @@
     }
 
     @Test
+    public void testWwanSelectorCallbackOnRequestEmergencyNetworkScanWithResetScan()
+            throws Exception {
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
+                mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
+                false, 0, TELECOM_CALL_ID1, null, null, null);
+
+        mDsc.selectDomain(attr);
+
+        IDomainSelector domainSelector = Mockito.mock(IDomainSelector.class);
+        transportCallback.onCreated(domainSelector);
+
+        IWwanSelectorCallback wwanCallback = onWwanSelected(transportCallback);
+
+        assertNotNull(wwanCallback);
+
+        int[] preferredNetworks = new int[] { EUTRAN, UTRAN };
+        int scanType = SCAN_TYPE_NO_PREFERENCE;
+        IWwanSelectorResultCallback resultCallback =
+                Mockito.mock(IWwanSelectorResultCallback.class);
+
+        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType,
+                true, resultCallback);
+        processAllMessages();
+
+        ArgumentCaptor<Message> msgCaptor = ArgumentCaptor.forClass(Message.class);
+
+        verify(mPhone).cancelEmergencyNetworkScan(eq(true), msgCaptor.capture());
+
+        verify(mPhone, times(0)).registerForEmergencyNetworkScan(any(), anyInt(), any());
+        verify(mPhone, times(0)).triggerEmergencyNetworkScan(any(), anyInt(), any());
+
+        Message msg = msgCaptor.getValue();
+
+        assertNotNull(msg);
+
+        AsyncResult unused = AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Integer> eventCaptor = ArgumentCaptor.forClass(Integer.class);
+
+        verify(mPhone).registerForEmergencyNetworkScan(
+                handlerCaptor.capture(), eventCaptor.capture(), any());
+
+        int[] expectedPreferredNetworks = new int[] { EUTRAN, UTRAN };
+
+        verify(mPhone).triggerEmergencyNetworkScan(eq(expectedPreferredNetworks),
+                eq(scanType), any());
+
+        Handler handler = handlerCaptor.getValue();
+        int event = eventCaptor.getValue();
+
+        assertNotNull(handler);
+
+        EmergencyRegistrationResult regResult =
+                new EmergencyRegistrationResult(UTRAN, 0, 0, true, false, 0, 0, "", "", "");
+        handler.sendMessage(handler.obtainMessage(event, new AsyncResult(null, regResult, null)));
+        processAllMessages();
+
+        verify(resultCallback).onComplete(eq(regResult));
+    }
+
+    @Test
+    public void testWwanSelectorCallbackOnRequestEmergencyNetworkScanWithResetScanDoneAndCancel()
+            throws Exception {
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
+                mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
+                false, 0, TELECOM_CALL_ID1, null, null, null);
+
+        mDsc.selectDomain(attr);
+
+        IDomainSelector domainSelector = Mockito.mock(IDomainSelector.class);
+        transportCallback.onCreated(domainSelector);
+
+        IWwanSelectorCallback wwanCallback = onWwanSelected(transportCallback);
+
+        assertNotNull(wwanCallback);
+
+        int[] preferredNetworks = new int[] { EUTRAN, UTRAN };
+        int scanType = SCAN_TYPE_NO_PREFERENCE;
+        IWwanSelectorResultCallback resultCallback =
+                Mockito.mock(IWwanSelectorResultCallback.class);
+
+        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType,
+                true, resultCallback);
+        processAllMessages();
+
+        ArgumentCaptor<Message> msgCaptor = ArgumentCaptor.forClass(Message.class);
+
+        verify(mPhone).cancelEmergencyNetworkScan(eq(true), msgCaptor.capture());
+        verify(mPhone, times(0)).registerForEmergencyNetworkScan(any(), anyInt(), any());
+        verify(mPhone, times(0)).triggerEmergencyNetworkScan(any(), anyInt(), any());
+
+        Message msg = msgCaptor.getValue();
+
+        assertNotNull(msg);
+
+        // Reset completes.
+        AsyncResult unused = AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        // Verify that scan is requested.
+        verify(mPhone).registerForEmergencyNetworkScan(any(), anyInt(), any());
+        verify(mPhone).triggerEmergencyNetworkScan(any(), anyInt(), any());
+
+        // Cancele scan after reset completes.
+        wwanCallback.onCancel();
+        processAllMessages();
+
+        // Verify scan request is canceled.
+        verify(mPhone).cancelEmergencyNetworkScan(eq(false), any());
+        verify(mPhone, times(2)).cancelEmergencyNetworkScan(anyBoolean(), any());
+    }
+
+    @Test
+    public void testWwanSelectorCallbackOnRequestEmergencyNetworkScanWithResetScanAndCancel()
+            throws Exception {
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
+                mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
+                false, 0, TELECOM_CALL_ID1, null, null, null);
+
+        mDsc.selectDomain(attr);
+
+        IDomainSelector domainSelector = Mockito.mock(IDomainSelector.class);
+        transportCallback.onCreated(domainSelector);
+
+        IWwanSelectorCallback wwanCallback = onWwanSelected(transportCallback);
+
+        assertNotNull(wwanCallback);
+
+        int[] preferredNetworks = new int[] { EUTRAN, UTRAN };
+        int scanType = SCAN_TYPE_NO_PREFERENCE;
+        IWwanSelectorResultCallback resultCallback =
+                Mockito.mock(IWwanSelectorResultCallback.class);
+
+        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType,
+                true, resultCallback);
+        processAllMessages();
+
+        ArgumentCaptor<Message> msgCaptor = ArgumentCaptor.forClass(Message.class);
+
+        verify(mPhone).cancelEmergencyNetworkScan(eq(true), msgCaptor.capture());
+        verify(mPhone, times(0)).registerForEmergencyNetworkScan(any(), anyInt(), any());
+        verify(mPhone, times(0)).triggerEmergencyNetworkScan(any(), anyInt(), any());
+
+        Message msg = msgCaptor.getValue();
+
+        assertNotNull(msg);
+
+        // Canceled before reset completes.
+        wwanCallback.onCancel();
+        processAllMessages();
+
+        // Verify there is no additional cancel.
+        verify(mPhone, times(1)).cancelEmergencyNetworkScan(anyBoolean(), any());
+
+        // Reset completes
+        AsyncResult unused = AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        // Verify there is no scan request after reset completes.
+        verify(mPhone, times(0)).registerForEmergencyNetworkScan(any(), anyInt(), any());
+        verify(mPhone, times(0)).triggerEmergencyNetworkScan(any(), anyInt(), any());
+    }
+
+    @Test
     @SmallTest
     public void testDomainSelectorCancelSelection() throws Exception {
         mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
@@ -255,7 +451,7 @@
 
         mDsc.cancelSelection();
 
-        verify(domainSelector).cancelSelection();
+        verify(domainSelector).finishSelection();
     }
 
     @Test
@@ -393,7 +589,8 @@
                 Mockito.mock(IWwanSelectorResultCallback.class);
 
         // 1st scan request from remote service
-        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType, resultCallback);
+        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType,
+                false, resultCallback);
         processAllMessages();
 
         ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
@@ -429,15 +626,16 @@
         // 2nd scan request
         IWwanSelectorResultCallback resultCallback2 =
                 Mockito.mock(IWwanSelectorResultCallback.class);
-        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType, resultCallback2);
+        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType,
+                false, resultCallback2);
         processAllMessages();
 
         // Verify that triggerEmergencyNetworkScan isn't called
         verify(mPhone, times(1)).triggerEmergencyNetworkScan(any(), anyInt(), any());
 
         // Result received
-        EmergencyRegResult regResult =
-                new EmergencyRegResult(UTRAN, 0, 0, true, false, 0, 0, "", "", "");
+        EmergencyRegistrationResult regResult =
+                new EmergencyRegistrationResult(UTRAN, 0, 0, true, false, 0, 0, "", "", "");
         handler.sendMessage(handler.obtainMessage(event, new AsyncResult(null, regResult, null)));
         processAllMessages();
 
@@ -476,7 +674,8 @@
                 Mockito.mock(IWwanSelectorResultCallback.class);
 
         // 1st scan request from remote service
-        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType, resultCallback);
+        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType,
+                false, resultCallback);
         processAllMessages();
 
         ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
@@ -500,8 +699,8 @@
         verify(mDomainSelectionController, times(1)).selectDomain(eq(attr), eq(transportCallback));
 
         // Result received
-        EmergencyRegResult regResult =
-                new EmergencyRegResult(UTRAN, 0, 0, true, false, 0, 0, "", "", "");
+        EmergencyRegistrationResult regResult =
+                new EmergencyRegistrationResult(UTRAN, 0, 0, true, false, 0, 0, "", "", "");
         handler.sendMessage(handler.obtainMessage(event, new AsyncResult(null, regResult, null)));
         processAllMessages();
 
@@ -517,7 +716,8 @@
         // 2nd scan request
         IWwanSelectorResultCallback resultCallback2 =
                 Mockito.mock(IWwanSelectorResultCallback.class);
-        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType, resultCallback2);
+        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType,
+                false, resultCallback2);
         processAllMessages();
 
         // Verify that triggerEmergencyNetworkScan is called
@@ -525,7 +725,7 @@
 
         // Result received
         regResult =
-                new EmergencyRegResult(EUTRAN, 0, 0, true, false, 0, 0, "", "", "");
+                new EmergencyRegistrationResult(EUTRAN, 0, 0, true, false, 0, 0, "", "", "");
         handler.sendMessage(handler.obtainMessage(event, new AsyncResult(null, regResult, null)));
         processAllMessages();
 
@@ -577,6 +777,7 @@
             boolean isEmergency, DomainSelectionController controller) throws Exception {
         DomainSelectionConnection dsc = new DomainSelectionConnection(phone,
                 selectorType, isEmergency, controller);
+        dsc.setTestMode(true);
         replaceInstance(DomainSelectionConnection.class, "mLooper",
                 dsc, mTestableLooper.getLooper());
         return dsc;
@@ -585,7 +786,7 @@
     private DomainSelectionService.SelectionAttributes getSelectionAttributes(
             int slotId, int subId, int selectorType, boolean isEmergency,
             boolean exited, int callFailCause, String callId, String number,
-            ImsReasonInfo imsReasonInfo, EmergencyRegResult regResult) {
+            ImsReasonInfo imsReasonInfo, EmergencyRegistrationResult regResult) {
         DomainSelectionService.SelectionAttributes.Builder builder =
                 new DomainSelectionService.SelectionAttributes.Builder(
                         slotId, subId, selectorType)
@@ -594,9 +795,11 @@
                 .setCsDisconnectCause(callFailCause);
 
         if (callId != null) builder.setCallId(callId);
-        if (number != null) builder.setNumber(number);
+        if (number != null) {
+            builder.setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null));
+        }
         if (imsReasonInfo != null) builder.setPsDisconnectCause(imsReasonInfo);
-        if (regResult != null) builder.setEmergencyRegResult(regResult);
+        if (regResult != null) builder.setEmergencyRegistrationResult(regResult);
 
         return builder.build();
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnectionTest.java
index 76de9c9..f893c35 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnectionTest.java
@@ -42,7 +42,7 @@
 import android.os.Message;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.DomainSelectionService;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.data.ApnSetting;
 import android.testing.AndroidTestingRunner;
@@ -95,6 +95,7 @@
         doReturn(mAnm).when(mPhone).getAccessNetworksManager();
         mEcDsc = new EmergencyCallDomainSelectionConnection(mPhone,
                 mDomainSelectionController, mEmergencyStateTracker);
+        mEcDsc.setTestMode(true);
         replaceInstance(DomainSelectionConnection.class, "mLooper",
                 mEcDsc, mTestableLooper.getLooper());
         mTransportCallback = mEcDsc.getTransportSelectorCallback();
@@ -113,7 +114,7 @@
         replaceInstance(EmergencyCallDomainSelectionConnection.class,
                 "mEmergencyStateTracker", mEcDsc, mEmergencyStateTracker);
 
-        EmergencyRegResult regResult = new EmergencyRegResult(
+        EmergencyRegistrationResult regResult = new EmergencyRegistrationResult(
                 EUTRAN, REGISTRATION_STATE_UNKNOWN,
                 NetworkRegistrationInfo.DOMAIN_PS,
                 true, false, 0, 0, "", "", "");
@@ -121,7 +122,7 @@
         DomainSelectionService.SelectionAttributes attr =
                 EmergencyCallDomainSelectionConnection.getSelectionAttributes(
                         mPhone.getPhoneId(), mPhone.getSubId(), false,
-                        TELECOM_CALL_ID1, "911", 0, null, regResult);
+                        TELECOM_CALL_ID1, "911", false, 0, null, regResult);
 
         CompletableFuture<Integer> future =
                 mEcDsc.createEmergencyConnection(attr, mConnectionCallback);
@@ -162,7 +163,7 @@
         replaceInstance(EmergencyCallDomainSelectionConnection.class,
                 "mEmergencyStateTracker", mEcDsc, mEmergencyStateTracker);
 
-        EmergencyRegResult regResult = new EmergencyRegResult(
+        EmergencyRegistrationResult regResult = new EmergencyRegistrationResult(
                 UTRAN, REGISTRATION_STATE_UNKNOWN,
                 NetworkRegistrationInfo.DOMAIN_CS,
                 true, false, 0, 0, "", "", "");
@@ -170,7 +171,7 @@
         DomainSelectionService.SelectionAttributes attr =
                 EmergencyCallDomainSelectionConnection.getSelectionAttributes(
                         mPhone.getPhoneId(), mPhone.getSubId(), false,
-                        TELECOM_CALL_ID1, "911", 0, null, regResult);
+                        TELECOM_CALL_ID1, "911", false, 0, null, regResult);
 
         CompletableFuture<Integer> future =
                 mEcDsc.createEmergencyConnection(attr, mConnectionCallback);
@@ -184,7 +185,7 @@
         IWwanSelectorCallback wwanCallback = onWwanSelected(mTransportCallback);
 
         assertFalse(future.isDone());
-        verify(mEmergencyStateTracker).onEmergencyTransportChanged(
+        verify(mEmergencyStateTracker).onEmergencyTransportChangedAndWait(
                 eq(EmergencyStateTracker.EMERGENCY_TYPE_CALL), eq(MODE_EMERGENCY_WWAN));
 
         wwanCallback.onDomainSelected(DOMAIN_CS, false);
@@ -200,7 +201,7 @@
         replaceInstance(EmergencyCallDomainSelectionConnection.class,
                 "mEmergencyStateTracker", mEcDsc, mEmergencyStateTracker);
 
-        EmergencyRegResult regResult = new EmergencyRegResult(
+        EmergencyRegistrationResult regResult = new EmergencyRegistrationResult(
                 EUTRAN, REGISTRATION_STATE_UNKNOWN,
                 NetworkRegistrationInfo.DOMAIN_PS,
                 true, true, 0, 0, "", "", "");
@@ -208,7 +209,7 @@
         DomainSelectionService.SelectionAttributes attr =
                 EmergencyCallDomainSelectionConnection.getSelectionAttributes(
                         mPhone.getPhoneId(), mPhone.getSubId(), false,
-                        TELECOM_CALL_ID1, "911", 0, null, regResult);
+                        TELECOM_CALL_ID1, "911", false, 0, null, regResult);
 
         CompletableFuture<Integer> future =
                 mEcDsc.createEmergencyConnection(attr, mConnectionCallback);
@@ -222,7 +223,7 @@
         IWwanSelectorCallback wwanCallback = onWwanSelected(mTransportCallback);
 
         assertFalse(future.isDone());
-        verify(mEmergencyStateTracker).onEmergencyTransportChanged(
+        verify(mEmergencyStateTracker).onEmergencyTransportChangedAndWait(
                 eq(EmergencyStateTracker.EMERGENCY_TYPE_CALL), eq(MODE_EMERGENCY_WWAN));
 
         wwanCallback.onDomainSelected(DOMAIN_PS, true);
@@ -234,7 +235,7 @@
     @Test
     @SmallTest
     public void testOnSelectionTerminated() throws Exception {
-        EmergencyRegResult regResult = new EmergencyRegResult(
+        EmergencyRegistrationResult regResult = new EmergencyRegistrationResult(
                 EUTRAN, REGISTRATION_STATE_UNKNOWN,
                 NetworkRegistrationInfo.DOMAIN_PS,
                 true, true, 0, 0, "", "", "");
@@ -242,7 +243,7 @@
         DomainSelectionService.SelectionAttributes attr =
                 EmergencyCallDomainSelectionConnection.getSelectionAttributes(
                         mPhone.getPhoneId(), mPhone.getSubId(), false,
-                        TELECOM_CALL_ID1, "911", 0, null, regResult);
+                        TELECOM_CALL_ID1, "911", false, 0, null, regResult);
 
         mEcDsc.createEmergencyConnection(attr, mConnectionCallback);
         mTransportCallback.onSelectionTerminated(ERROR_UNSPECIFIED);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnectionTest.java
index c25aeb9..4f63be0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnectionTest.java
@@ -444,7 +444,7 @@
 
         assertFalse(future.isDone());
         verify(mAnm).unregisterForQualifiedNetworksChanged(any(Handler.class));
-        verify(mDomainSelector).cancelSelection();
+        verify(mDomainSelector).finishSelection();
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnectionTest.java
index f05943f..72d8524 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnectionTest.java
@@ -188,10 +188,10 @@
                 NormalCallDomainSelectionConnection.getSelectionAttributes(1, 2,
                         TELECOM_CALL_ID1, "123", false, 10, imsReasonInfo);
 
-        assertEquals(1, attributes.getSlotId());
-        assertEquals(2, attributes.getSubId());
+        assertEquals(1, attributes.getSlotIndex());
+        assertEquals(2, attributes.getSubscriptionId());
         assertEquals(TELECOM_CALL_ID1, attributes.getCallId());
-        assertEquals("123", attributes.getNumber());
+        assertEquals("123", attributes.getAddress().getSchemeSpecificPart());
         assertEquals(false, attributes.isVideoCall());
         assertEquals(false, attributes.isEmergency());
         assertEquals(SELECTOR_TYPE_CALLING, attributes.getSelectorType());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/OWNERS b/tests/telephonytests/src/com/android/internal/telephony/domainselection/OWNERS
index b9112be..2a76770 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/domainselection/OWNERS
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/OWNERS
@@ -6,3 +6,4 @@
 mkoon@google.com
 seheele@google.com
 radhikaagrawal@google.com
+jdyou@google.com
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/SmsDomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/SmsDomainSelectionConnectionTest.java
index 05291e2..5799dd8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/domainselection/SmsDomainSelectionConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/SmsDomainSelectionConnectionTest.java
@@ -205,7 +205,7 @@
 
         mDsConnection.finishSelection();
 
-        verify(mDomainSelector).cancelSelection();
+        verify(mDomainSelector).finishSelection();
     }
 
     private void setUpTestableLooper() throws Exception {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
index 27b53ad..105f2bf 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
@@ -23,6 +23,7 @@
 import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_CALLBACK;
 import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WLAN;
 import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WWAN;
+import static com.android.internal.telephony.emergency.EmergencyStateTracker.DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -52,9 +53,11 @@
 import android.os.Message;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
 import android.telephony.DisconnectCause;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyCallback;
@@ -90,12 +93,10 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class EmergencyStateTrackerTest extends TelephonyTest {
-    private static final String TEST_CALL_ID = "TC@TEST1";
-    private static final String TEST_CALL_ID_2 = "TC@TEST2";
     private static final String TEST_SMS_ID = "1111";
     private static final String TEST_SMS_ID_2 = "2222";
     private static final long TEST_ECM_EXIT_TIMEOUT_MS = 500;
-    private static final EmergencyRegResult E_REG_RESULT = new EmergencyRegResult(
+    private static final EmergencyRegistrationResult E_REG_RESULT = new EmergencyRegistrationResult(
             EUTRAN, REGISTRATION_STATE_HOME, DOMAIN_CS_PS, true, true, 0, 1, "001", "01", "US");
 
     @Mock EmergencyStateTracker.PhoneFactoryProxy mPhoneFactoryProxy;
@@ -103,6 +104,8 @@
     @Mock EmergencyStateTracker.TelephonyManagerProxy mTelephonyManagerProxy;
     @Mock PhoneSwitcher mPhoneSwitcher;
     @Mock RadioOnHelper mRadioOnHelper;
+    @Mock android.telecom.Connection mTestConnection1;
+    @Mock android.telecom.Connection mTestConnection2;
 
     @Before
     public void setUp() throws Exception {
@@ -151,22 +154,93 @@
                 false /* isRadioOn */);
         setConfigForDdsSwitch(testPhone, null,
                 CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY, "150");
+        ServiceState ss = mock(ServiceState.class);
+        doReturn(ss).when(mSST).getServiceState();
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .build();
+        doReturn(nri).when(ss).getNetworkRegistrationInfo(anyInt(), anyInt());
         // Spy is used to capture consumer in delayDialForDdsSwitch
         EmergencyStateTracker spyEst = spy(emergencyStateTracker);
-        CompletableFuture<Integer> unused = spyEst.startEmergencyCall(testPhone, TEST_CALL_ID,
-                false);
+        CompletableFuture<Integer> unused = spyEst.startEmergencyCall(testPhone,
+                mTestConnection1, false);
 
         // startEmergencyCall should trigger radio on
         ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
                 .forClass(RadioOnStateListener.Callback.class);
         verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true), eq(testPhone),
-                eq(false), eq(0));
-        // isOkToCall() should return true once radio is on
+                eq(false), eq(DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS));
+        // isOkToCall() should return true when IN_SERVICE state
         assertFalse(callback.getValue()
                 .isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
         when(mSST.isRadioOn()).thenReturn(true);
-        assertTrue(callback.getValue()
+        assertFalse(callback.getValue()
                 .isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
+
+        nri = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setRegistrationState(REGISTRATION_STATE_HOME)
+                .build();
+        doReturn(nri).when(ss).getNetworkRegistrationInfo(anyInt(), anyInt());
+
+        assertTrue(callback.getValue()
+                .isOkToCall(testPhone, ServiceState.STATE_IN_SERVICE, false));
+        // Once radio on is complete, trigger delay dial
+        callback.getValue().onComplete(null, true);
+        ArgumentCaptor<Consumer<Boolean>> completeConsumer = ArgumentCaptor
+                .forClass(Consumer.class);
+        verify(spyEst).switchDdsDelayed(eq(testPhone), completeConsumer.capture());
+        verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(testPhone.getPhoneId()),
+                eq(150) /* extensionTime */, any());
+        // After dds switch completes successfully, set emergency mode
+        completeConsumer.getValue().accept(true);
+        verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any());
+    }
+
+    /**
+     * Test that the EmergencyStateTracker turns on radio, performs a DDS switch and sets emergency
+     * mode switch when we are not roaming and the carrier only supports SUPL over the data plane.
+     */
+    @Test
+    @SmallTest
+    public void startEmergencyCall_radioOff_turnOnRadioTimeoutSwitchDdsAndSetEmergencyMode() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                true /* isSuplDdsSwitchRequiredForEmergencyCall */);
+        // Create test Phones and set radio off
+        Phone testPhone = setupTestPhoneForEmergencyCall(false /* isRoaming */,
+                false /* isRadioOn */);
+        setConfigForDdsSwitch(testPhone, null,
+                CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY, "150");
+        ServiceState ss = mock(ServiceState.class);
+        doReturn(ss).when(mSST).getServiceState();
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .build();
+        doReturn(nri).when(ss).getNetworkRegistrationInfo(anyInt(), anyInt());
+        // Spy is used to capture consumer in delayDialForDdsSwitch
+        EmergencyStateTracker spyEst = spy(emergencyStateTracker);
+        CompletableFuture<Integer> unused = spyEst.startEmergencyCall(testPhone,
+                mTestConnection1, false);
+
+        // startEmergencyCall should trigger radio on
+        ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
+                .forClass(RadioOnStateListener.Callback.class);
+        verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true), eq(testPhone),
+                eq(false), eq(DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS));
+        // onTimeout should return true when radion on
+        assertFalse(callback.getValue()
+                .isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
+        assertFalse(callback.getValue()
+                .onTimeout(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
+        when(mSST.isRadioOn()).thenReturn(true);
+
+        assertFalse(callback.getValue()
+                .isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
+        assertTrue(callback.getValue()
+                .onTimeout(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
         // Once radio on is complete, trigger delay dial
         callback.getValue().onComplete(null, true);
         ArgumentCaptor<Consumer<Boolean>> completeConsumer = ArgumentCaptor
@@ -193,13 +267,13 @@
                 false /* isRadioOn */);
 
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // startEmergencyCall should trigger radio on
         ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
                 .forClass(RadioOnStateListener.Callback.class);
         verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true), eq(testPhone),
-                eq(false), eq(0));
+                eq(false), eq(DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS));
         // Verify future completes with DisconnectCause.POWER_OFF if radio not ready
         CompletableFuture<Void> unused = future.thenAccept((result) -> {
             assertEquals((Integer) result, (Integer) DisconnectCause.POWER_OFF);
@@ -228,8 +302,8 @@
                 CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY, "150");
         // Spy is used to capture consumer in delayDialForDdsSwitch
         EmergencyStateTracker spyEst = spy(emergencyStateTracker);
-        CompletableFuture<Integer> unused = spyEst.startEmergencyCall(testPhone, TEST_CALL_ID,
-                false);
+        CompletableFuture<Integer> unused = spyEst.startEmergencyCall(testPhone,
+                mTestConnection1, false);
 
         // startEmergencyCall should trigger radio on
         ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
@@ -270,7 +344,7 @@
         when(mSatelliteController.isSatelliteEnabled()).thenReturn(true);
 
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // startEmergencyCall should trigger satellite modem off
         ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
@@ -300,7 +374,7 @@
                 CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK, "0");
 
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // Radio already on so shouldn't trigger this
         verify(mRadioOnHelper, never()).triggerRadioOnAndListen(any(), anyBoolean(), any(),
@@ -325,7 +399,7 @@
                 CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY, "0");
 
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // non-DDS supports SUPL, so no DDS switch
         verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any());
@@ -347,7 +421,7 @@
                 CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY, "0");
 
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // Is roaming, so no DDS switch
         verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any());
@@ -374,7 +448,7 @@
                 CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK, "0");
 
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // Verify DDS switch
         verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(0) /* phoneId */,
@@ -402,7 +476,7 @@
                 CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK, "0");
 
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // Verify DDS switch
         verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(0) /* phoneId */,
@@ -411,7 +485,7 @@
 
     /**
      * Test that once EmergencyStateTracker handler receives set emergency mode done message it sets
-     * IsInEmergencyCall to true, sets LastEmergencyRegResult and completes future with
+     * IsInEmergencyCall to true, sets LastEmergencyRegistrationResult and completes future with
      * DisconnectCause.NOT_DISCONNECTED.
      */
     @Test
@@ -425,7 +499,7 @@
         setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT);
         // Call startEmergencyCall() to set testPhone
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         // Verify future completes with DisconnectCause.NOT_DISCONNECTED
         CompletableFuture<Void> unused = future.thenAccept((result) -> {
             assertEquals((Integer) result, (Integer) DisconnectCause.NOT_DISCONNECTED);
@@ -435,7 +509,7 @@
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyCall());
-        assertTrue(emergencyStateTracker.getEmergencyRegResult().equals(E_REG_RESULT));
+        assertTrue(emergencyStateTracker.getEmergencyRegistrationResult().equals(E_REG_RESULT));
         verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
     }
 
@@ -455,13 +529,13 @@
         setUpAsyncResultForExitEmergencyMode(testPhone);
         // Call startEmergencyCall() to set testPhone
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         assertFalse(emergencyStateTracker.isInEmergencyCall());
@@ -482,15 +556,15 @@
                 /* isRadioOn= */ true);
         // Call startEmergencyCall() to set testPhone
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
         // set domain
         emergencyStateTracker.onEmergencyCallDomainUpdated(PhoneConstants.PHONE_TYPE_IMS,
-                TEST_CALL_ID);
+                mTestConnection1);
         // End call to enter ECM
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         // Make sure CS ECBM is true
         assertTrue(emergencyStateTracker.isInEcm());
@@ -512,15 +586,15 @@
                 /* isRadioOn= */ true);
         // Call startEmergencyCall() to set testPhone
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
         // set domain
         emergencyStateTracker.onEmergencyCallDomainUpdated(PhoneConstants.PHONE_TYPE_CDMA,
-                TEST_CALL_ID);
+                mTestConnection1);
         // End call to enter ECM
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         // Make sure IMS ECBM is true
         assertTrue(emergencyStateTracker.isInEcm());
@@ -544,15 +618,15 @@
         doReturn(PhoneConstants.PHONE_TYPE_GSM).when(testPhone).getPhoneType();
         // Call startEmergencyCall() to set testPhone
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
         // set domain
         emergencyStateTracker.onEmergencyCallDomainUpdated(PhoneConstants.PHONE_TYPE_CDMA,
-                TEST_CALL_ID);
+                mTestConnection1);
         // End call to enter ECM
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         assertTrue(emergencyStateTracker.isInEcm());
         assertFalse(emergencyStateTracker.isInCdmaEcm());
@@ -572,7 +646,7 @@
                 /* isRadioOn= */ true );
         // Call startEmergencyCall() to set testPhone
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         emergencyStateTracker.onEmergencyTransportChanged(
                 EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN);
@@ -581,6 +655,27 @@
     }
 
     /**
+     * Test that onEmergencyTransportChangedAndWait sets the new emergency mode.
+     */
+    @Test
+    @SmallTest
+    public void onEmergencyTransportChangedAndWait_setEmergencyMode() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        // Create test Phones
+        Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        // Call startEmergencyCall() to set testPhone
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                mTestConnection1, false);
+
+        emergencyStateTracker.onEmergencyTransportChangedAndWait(
+                EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN);
+
+        verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any());
+    }
+
+    /**
      * Test that after endCall() is called, EmergencyStateTracker will enter ECM if the call was
      * ACTIVE and send related intents.
      */
@@ -595,15 +690,15 @@
                 /* isRadioOn= */ true);
         // Start emergency call then enter ECM
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
         // Set ecm as supported
         setEcmSupportedConfig(testPhone, true);
 
         assertFalse(emergencyStateTracker.isInEcm());
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         assertTrue(emergencyStateTracker.isInEcm());
         // Verify intents are sent that ECM is entered
@@ -630,13 +725,13 @@
                 /* isRadioOn= */ true);
         // Start emergency call then enter ECM
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         // Call does not reach ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.IDLE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.IDLE, mTestConnection1);
         // Set ecm as supported
         setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         assertFalse(emergencyStateTracker.isInEcm());
     }
@@ -657,15 +752,15 @@
         setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT);
         setUpAsyncResultForExitEmergencyMode(testPhone);
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
         // Set ecm as supported
         setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
 
         processAllMessages();
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         assertTrue(emergencyStateTracker.isInEcm());
 
@@ -692,17 +787,17 @@
         setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT);
         setUpAsyncResultForExitEmergencyMode(testPhone);
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
         emergencyStateTracker.onEmergencyCallDomainUpdated(
-                PhoneConstants.PHONE_TYPE_IMS, TEST_CALL_ID);
+                PhoneConstants.PHONE_TYPE_IMS, mTestConnection1);
         // Set ecm as supported
         setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
 
         processAllMessages();
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         assertTrue(emergencyStateTracker.isInEcm());
 
@@ -748,22 +843,22 @@
         verify(testPhone, times(0)).exitEmergencyMode(any(Message.class));
 
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         verify(testPhone, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
         verify(testPhone, times(0)).exitEmergencyMode(any(Message.class));
 
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
         emergencyStateTracker.onEmergencyCallDomainUpdated(
-                PhoneConstants.PHONE_TYPE_IMS, TEST_CALL_ID);
+                PhoneConstants.PHONE_TYPE_IMS, mTestConnection1);
         // Set ecm as supported
         setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
 
         processAllMessages();
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         assertTrue(emergencyStateTracker.isInEcm());
 
@@ -792,7 +887,7 @@
         replaceInstance(EmergencyStateTracker.class, "mContext", emergencyStateTracker, mContext);
 
         // dial on the same slot
-        unused = emergencyStateTracker.startEmergencyCall(testPhone, TEST_CALL_ID, false);
+        unused = emergencyStateTracker.startEmergencyCall(testPhone, mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
@@ -824,22 +919,22 @@
         verify(testPhone, times(0)).exitEmergencyMode(any(Message.class));
 
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         verify(testPhone, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
         verify(testPhone, times(0)).exitEmergencyMode(any(Message.class));
 
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
         emergencyStateTracker.onEmergencyCallDomainUpdated(
-                PhoneConstants.PHONE_TYPE_IMS, TEST_CALL_ID);
+                PhoneConstants.PHONE_TYPE_IMS, mTestConnection1);
         // Set ecm as supported
         setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
 
         processAllMessages();
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         assertTrue(emergencyStateTracker.isInEcm());
 
@@ -872,7 +967,7 @@
         replaceInstance(EmergencyStateTracker.class, "mContext", emergencyStateTracker, mContext);
 
         // dial on the other slot
-        unused = emergencyStateTracker.startEmergencyCall(phone1, TEST_CALL_ID, false);
+        unused = emergencyStateTracker.startEmergencyCall(phone1, mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
@@ -902,15 +997,15 @@
         setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT);
         setUpAsyncResultForExitEmergencyMode(testPhone);
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
         // Set ecm as supported
         setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
 
         processAllMessages();
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         assertTrue(emergencyStateTracker.isInEcm());
 
@@ -939,16 +1034,16 @@
         setUpAsyncResultForExitEmergencyMode(testPhone);
         // Start emergency call then enter ECM
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
         emergencyStateTracker.onEmergencyCallDomainUpdated(
-                PhoneConstants.PHONE_TYPE_IMS, TEST_CALL_ID);
+                PhoneConstants.PHONE_TYPE_IMS, mTestConnection1);
         // Set ecm as supported
         setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
         // End call to enter ECM
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         // verify ecbm states are correct
@@ -984,7 +1079,7 @@
         Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
                 /* isRadioOn= */ true);
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         Handler handler = new Handler(Looper.getMainLooper());
         handler.post(() -> {
@@ -1004,15 +1099,13 @@
         Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
                 /* isRadioOn= */ true);
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, true);
+                mTestConnection1, true);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
-        assertTrue(emergencyStateTracker.isInEmergencyCall());
-        // Expect: DisconnectCause#NOT_DISCONNECTED.
-        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
-                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
-        verify(phone0, never()).setEmergencyMode(anyInt(), any(Message.class));
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+        assertFalse(future.isDone());
+        verify(phone0).setEmergencyMode(anyInt(), any(Message.class));
     }
 
     @Test
@@ -1025,18 +1118,18 @@
         setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
         // First active call
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
 
         // Second starting call
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID_2, false);
+                mTestConnection2, false);
 
         // Returns DisconnectCause#NOT_DISCONNECTED immediately.
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
@@ -1055,22 +1148,22 @@
 
         // First active call
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEcm());
 
         // Second emergency call started.
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID_2, false);
+                mTestConnection2, false);
 
         // Returns DisconnectCause#NOT_DISCONNECTED immediately.
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
@@ -1088,7 +1181,7 @@
 
         // First emergency call
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
@@ -1098,7 +1191,7 @@
         // Second emergency call
         Phone phone1 = getPhone(1);
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone1,
-                TEST_CALL_ID_2, false);
+                mTestConnection2, false);
 
         // Returns DisconnectCause#ERROR_UNSPECIFIED immediately.
         assertEquals(future.getNow(DisconnectCause.NOT_DISCONNECTED),
@@ -1117,7 +1210,7 @@
 
         // First active call
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
@@ -1126,15 +1219,15 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEcm());
         assertFalse(emergencyStateTracker.isInEmergencyCall());
 
         // Second emergency call started.
-        future = emergencyStateTracker.startEmergencyCall(phone0, TEST_CALL_ID_2, false);
+        future = emergencyStateTracker.startEmergencyCall(phone0, mTestConnection2, false);
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         // Returns DisconnectCause#NOT_DISCONNECTED immediately.
@@ -1143,7 +1236,7 @@
 
         emergencyStateTracker.onEmergencyTransportChanged(
                 EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WLAN);
-        emergencyStateTracker.endCall(TEST_CALL_ID_2);
+        emergencyStateTracker.endCall(mTestConnection2);
         processAllMessages();
 
         // At this time, ECM is still running so still in ECM.
@@ -1163,7 +1256,7 @@
                 /* isRadioOn= */ true);
         // Call startEmergencyCall() to set testPhone
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // Set emergency transport
         emergencyStateTracker.onEmergencyTransportChanged(
@@ -1171,12 +1264,12 @@
 
         // Set call properties
         emergencyStateTracker.onEmergencyCallPropertiesChanged(
-                android.telecom.Connection.PROPERTY_WIFI, TEST_CALL_ID);
+                android.telecom.Connection.PROPERTY_WIFI, mTestConnection1);
 
         verify(testPhone, times(0)).cancelEmergencyNetworkScan(anyBoolean(), any());
 
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
 
         verify(testPhone, times(1)).cancelEmergencyNetworkScan(eq(true), any());
     }
@@ -1196,7 +1289,7 @@
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        assertTrue(emergencyStateTracker.getEmergencyRegResult().equals(E_REG_RESULT));
+        assertTrue(emergencyStateTracker.getEmergencyRegistrationResult().equals(E_REG_RESULT));
         // Expect: DisconnectCause#NOT_DISCONNECTED.
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
@@ -1217,7 +1310,7 @@
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        assertTrue(emergencyStateTracker.getEmergencyRegResult().equals(E_REG_RESULT));
+        assertTrue(emergencyStateTracker.getEmergencyRegistrationResult().equals(E_REG_RESULT));
         // Expect: DisconnectCause#NOT_DISCONNECTED.
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
@@ -1251,7 +1344,7 @@
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
-        assertTrue(emergencyStateTracker.getEmergencyRegResult().equals(E_REG_RESULT));
+        assertTrue(emergencyStateTracker.getEmergencyRegistrationResult().equals(E_REG_RESULT));
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WLAN), any(Message.class));
     }
 
@@ -1327,14 +1420,14 @@
         setEcmSupportedConfig(phone0, true);
         // Emergency call is ended and the emergency callback is entered.
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         assertTrue(emergencyStateTracker.isInEcm());
         assertFalse(emergencyStateTracker.isInEmergencyCall());
@@ -1385,14 +1478,14 @@
         setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
         // Emergency call is in active.
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
 
         // Emergency SMS is being started.
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
@@ -1412,7 +1505,7 @@
                 /* isRadioOn= */ true);
         // Emergency call is in progress.
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
@@ -1457,7 +1550,7 @@
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
         // Emergency call is being started.
-        future = emergencyStateTracker.startEmergencyCall(phone0, TEST_CALL_ID, false);
+        future = emergencyStateTracker.startEmergencyCall(phone0, mTestConnection1, false);
         processAllMessages();
 
         verify(phone0).exitEmergencyMode(any(Message.class));
@@ -1488,7 +1581,7 @@
 
         // Emergency call is being started.
         CompletableFuture<Integer> callFuture = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         assertFalse(smsFuture.isDone());
         assertFalse(callFuture.isDone());
@@ -1500,7 +1593,7 @@
         processAllMessages();
 
         // Exit emergency mode and set the emergency mode again by the call when the exit result
-        // is received for obtaining the latest EmergencyRegResult.
+        // is received for obtaining the latest EmergencyRegistrationResult.
         verify(phone0).exitEmergencyMode(any(Message.class));
         ArgumentCaptor<Message> callCaptor = ArgumentCaptor.forClass(Message.class);
         verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), callCaptor.capture());
@@ -1530,14 +1623,14 @@
         setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
         // Emergency call is in active.
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
 
         // Emergency SMS is being started using the different phone.
         Phone phone1 = getPhone(1);
@@ -1571,7 +1664,7 @@
         // Emergency call is being started using the different phone.
         Phone phone1 = getPhone(1);
         setUpAsyncResultForSetEmergencyMode(phone1, E_REG_RESULT);
-        future = emergencyStateTracker.startEmergencyCall(phone1, TEST_CALL_ID, false);
+        future = emergencyStateTracker.startEmergencyCall(phone1, mTestConnection1, false);
         processAllMessages();
 
         verify(phone0).exitEmergencyMode(any(Message.class));
@@ -1604,7 +1697,7 @@
         // Emergency call is being started using the different phone.
         Phone phone1 = getPhone(1);
         CompletableFuture<Integer> callFuture = emergencyStateTracker.startEmergencyCall(phone1,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         assertFalse(smsFuture.isDone());
         assertFalse(callFuture.isDone());
@@ -1616,7 +1709,7 @@
         processAllMessages();
 
         // Exit emergency mode and set the emergency mode again by the call when the exit result
-        // is received for obtaining the latest EmergencyRegResult.
+        // is received for obtaining the latest EmergencyRegistrationResult.
         verify(phone0).exitEmergencyMode(any(Message.class));
         ArgumentCaptor<Message> callCaptor = ArgumentCaptor.forClass(Message.class);
         verify(phone1).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), callCaptor.capture());
@@ -1648,14 +1741,14 @@
         setEcmSupportedConfig(phone0, false);
         // Emergency call is in active.
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
 
         // Emergency SMS is being started.
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
@@ -1665,7 +1758,7 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
 
@@ -1689,14 +1782,14 @@
         setEcmSupportedConfig(phone0, false);
         // Emergency call is in active.
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
 
         // Emergency SMS is being started.
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
@@ -1710,7 +1803,7 @@
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         assertFalse(emergencyStateTracker.isInEmergencyMode());
@@ -1730,14 +1823,14 @@
         setEcmSupportedConfig(phone0, true);
         // Emergency call is in active.
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
 
         // Emergency SMS is being started.
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
@@ -1747,7 +1840,7 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEcm());
@@ -1782,14 +1875,14 @@
         setEcmSupportedConfig(phone0, true);
         // Emergency call is in active.
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
 
         // Emergency SMS is being started.
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
@@ -1799,7 +1892,7 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
@@ -1838,14 +1931,14 @@
         setEcmSupportedConfig(phone0, true);
         // Emergency call is in active.
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
 
         // Emergency SMS is being started.
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
@@ -1859,7 +1952,7 @@
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
@@ -1888,7 +1981,7 @@
         EmergencyStateTracker testEst = setupEmergencyStateTracker(
                 false /* isSuplDdsSwitchRequiredForEmergencyCall */);
 
-        assertNotNull(testEst.startEmergencyCall(phone, TEST_CALL_ID, false));
+        assertNotNull(testEst.startEmergencyCall(phone, mTestConnection1, false));
 
         ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
@@ -1973,17 +2066,17 @@
         setUpAsyncResultForExitEmergencyMode(testPhone);
         // Start emergency call then enter ECM
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
         emergencyStateTracker.onEmergencyCallDomainUpdated(
-                PhoneConstants.PHONE_TYPE_IMS, TEST_CALL_ID);
+                PhoneConstants.PHONE_TYPE_IMS, mTestConnection1);
         // Set ecm as supported
         setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
         // End call to enter ECM
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         // verify ecbm states are correct
@@ -1993,7 +2086,7 @@
 
         // 2nd call while in emergency callback mode
         unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         emergencyStateTracker.onEmergencyTransportChanged(
                 EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN);
         processAllMessages();
@@ -2011,11 +2104,11 @@
         verify(testPhone).notifyEcbmTimerReset(eq(Boolean.TRUE));
 
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
         emergencyStateTracker.onEmergencyCallDomainUpdated(
-                PhoneConstants.PHONE_TYPE_IMS, TEST_CALL_ID);
+                PhoneConstants.PHONE_TYPE_IMS, mTestConnection1);
         // End call to enter ECM
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         // verify ecbm states are correct
@@ -2056,17 +2149,17 @@
         setUpAsyncResultForExitEmergencyMode(testPhone);
         // Start emergency call then enter ECM
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
         emergencyStateTracker.onEmergencyCallDomainUpdated(
-                PhoneConstants.PHONE_TYPE_IMS, TEST_CALL_ID);
+                PhoneConstants.PHONE_TYPE_IMS, mTestConnection1);
         // Set ecm as supported
         setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
         // End call to enter ECM
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         // verify ecbm states are correct
@@ -2076,7 +2169,7 @@
 
         // 2nd call while in emergency callback mode
         unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         emergencyStateTracker.onEmergencyTransportChanged(
                 EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN);
         processAllMessages();
@@ -2094,7 +2187,7 @@
         verify(testPhone).notifyEcbmTimerReset(eq(Boolean.TRUE));
 
         // End call to return to ECM
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         // verify ecbm states are correct
@@ -2191,7 +2284,8 @@
                 .putString(CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, esExtensionSec);
     }
 
-    private void setUpAsyncResultForSetEmergencyMode(Phone phone, EmergencyRegResult regResult) {
+    private void setUpAsyncResultForSetEmergencyMode(Phone phone,
+            EmergencyRegistrationResult regResult) {
         doAnswer((invocation) -> {
             Object[] args = invocation.getArguments();
             final Message msg = (Message) args[1];
diff --git a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccConnectorTest.java b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccConnectorTest.java
index d4850c8..ca4576f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccConnectorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccConnectorTest.java
@@ -40,6 +40,7 @@
 import android.os.test.TestLooper;
 import android.service.euicc.EuiccService;
 import android.service.euicc.IEuiccService;
+import android.service.euicc.IGetAvailableMemoryInBytesCallback;
 import android.service.euicc.IGetEidCallback;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -70,6 +71,7 @@
 
     private static final int CARD_ID = 15;
     private static final int PORT_INDEX = 0;
+    private static final long AVAILABLE_MEMORY = 123L;
 
     @Before
     public void setUp() throws Exception {
@@ -136,6 +138,31 @@
     }
 
     @Test
+    public void testInitialState_forAvailableMemory_commandRejected() {
+        prepareEuiccApp(
+                false /* hasPermission */,
+                false /* requiresBindPermission */,
+                false /* hasPriority */);
+        mConnector = new EuiccConnector(mContext, mLooper.getLooper());
+        final AtomicBoolean called = new AtomicBoolean(false);
+        mConnector.getAvailableMemoryInBytes(
+                CARD_ID,
+                new EuiccConnector.GetAvailableMemoryInBytesCommandCallback() {
+                    @Override
+                    public void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes) {
+                        fail("Command should have failed");
+                    }
+
+                    @Override
+                    public void onEuiccServiceUnavailable() {
+                        assertTrue("Callback called twice", called.compareAndSet(false, true));
+                    }
+                });
+        mLooper.dispatchAll();
+        assertTrue(called.get());
+    }
+
+    @Test
     public void testInitialState_switchCommandRejected() {
         prepareEuiccApp(false /* hasPermission */, false /* requiresBindPermission */,
                 false /* hasPriority */);
@@ -236,6 +263,47 @@
     }
 
     @Test
+    public void testCommandDispatch_forAvailableMemory_success() throws Exception {
+        prepareEuiccApp(
+                true /* hasPermission */,
+                true /* requiresBindPermission */,
+                true /* hasPriority */);
+        mConnector = new EuiccConnector(mContext, mLooper.getLooper());
+        doAnswer(
+                new Answer<Void>() {
+                    @Override
+                    public Void answer(InvocationOnMock invocation) throws Exception {
+                        IGetAvailableMemoryInBytesCallback callback =
+                                invocation.getArgument(1);
+                        callback.onSuccess(AVAILABLE_MEMORY);
+                        return null;
+                    }
+                })
+                .when(mEuiccService)
+                .getAvailableMemoryInBytes(
+                        anyInt(), Mockito.<IGetAvailableMemoryInBytesCallback>any());
+        final AtomicReference<Long> availableMemoryInBytesRef = new AtomicReference<>();
+        mConnector.getAvailableMemoryInBytes(
+                CARD_ID,
+                new EuiccConnector.GetAvailableMemoryInBytesCommandCallback() {
+                    @Override
+                    public void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes) {
+                        if (availableMemoryInBytesRef.get() != null) {
+                            fail("Callback called twice");
+                        }
+                        availableMemoryInBytesRef.set(availableMemoryInBytes);
+                    }
+
+                    @Override
+                    public void onEuiccServiceUnavailable() {
+                        fail("Command should have succeeded");
+                    }
+                });
+        mLooper.dispatchAll();
+        assertEquals(AVAILABLE_MEMORY, availableMemoryInBytesRef.get().longValue());
+    }
+
+    @Test
     public void testCommandDispatch_remoteException() throws Exception {
         prepareEuiccApp(true /* hasPermission */, true /* requiresBindPermission */,
                 true /* hasPriority */);
@@ -259,6 +327,35 @@
     }
 
     @Test
+    public void testCommandDispatch_forAvailableMemory_remoteException() throws Exception {
+        prepareEuiccApp(
+                true /* hasPermission */,
+                true /* requiresBindPermission */,
+                true /* hasPriority */);
+        mConnector = new EuiccConnector(mContext, mLooper.getLooper());
+        doThrow(new RemoteException("failure"))
+                .when(mEuiccService)
+                .getAvailableMemoryInBytes(
+                        anyInt(), Mockito.<IGetAvailableMemoryInBytesCallback>any());
+        final AtomicBoolean called = new AtomicBoolean(false);
+        mConnector.getAvailableMemoryInBytes(
+                CARD_ID,
+                new EuiccConnector.GetAvailableMemoryInBytesCommandCallback() {
+                    @Override
+                    public void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes) {
+                        fail("Command should have failed");
+                    }
+
+                    @Override
+                    public void onEuiccServiceUnavailable() {
+                        assertTrue("Callback called twice", called.compareAndSet(false, true));
+                    }
+                });
+        mLooper.dispatchAll();
+        assertTrue(called.get());
+    }
+
+    @Test
     public void testCommandDispatch_processDied() throws Exception {
         // Kick off the asynchronous command.
         prepareEuiccApp(true /* hasPermission */, true /* requiresBindPermission */,
@@ -288,6 +385,39 @@
     }
 
     @Test
+    public void testCommandDispatch_forAvailableMemory_processDied() throws Exception {
+        // Kick off the asynchronous command.
+        prepareEuiccApp(
+                true /* hasPermission */,
+                true /* requiresBindPermission */,
+                true /* hasPriority */);
+        mConnector = new EuiccConnector(mContext, mLooper.getLooper());
+        final AtomicBoolean called = new AtomicBoolean(false);
+        mConnector.getAvailableMemoryInBytes(
+                CARD_ID,
+                new EuiccConnector.GetAvailableMemoryInBytesCommandCallback() {
+                    @Override
+                    public void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes) {
+                        fail("Unexpected command success callback");
+                    }
+
+                    @Override
+                    public void onEuiccServiceUnavailable() {
+                        assertTrue("Callback called twice", called.compareAndSet(false, true));
+                    }
+                });
+        mLooper.dispatchAll();
+        assertFalse(called.get());
+
+        // Now, pretend the remote process died.
+        mConnector.onServiceDisconnected(null /* name */);
+        mLooper.dispatchAll();
+
+        // Callback should have been called.
+        assertTrue(called.get());
+    }
+
+    @Test
     public void testLinger() throws Exception {
         prepareEuiccApp(true /* hasPermission */, true /* requiresBindPermission */,
                 true /* hasPriority */);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
index a493247..d5ce447 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
@@ -41,6 +41,7 @@
 import android.Manifest;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
+import android.app.admin.flags.Flags;
 import android.compat.testing.PlatformCompatChangeRule;
 import android.content.Context;
 import android.content.Intent;
@@ -49,6 +50,8 @@
 import android.content.pm.Signature;
 import android.os.Parcelable;
 import android.os.RemoteException;
+import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.service.euicc.DownloadSubscriptionResult;
 import android.service.euicc.EuiccService;
@@ -94,11 +97,15 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
 public class EuiccControllerTest extends TelephonyTest {
     @Rule
     public TestRule compatChangeRule = new PlatformCompatChangeRule();
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final DownloadableSubscription SUBSCRIPTION =
             DownloadableSubscription.forActivationCode("abcde");
 
@@ -134,6 +141,7 @@
     private static final String ICC_ID = "54321";
     private static final int CARD_ID = 25;
     private static final int REMOVABLE_CARD_ID = 26;
+    private static final long AVAILABLE_MEMORY = 123L;
 
     // Mocked classes
     private EuiccConnector mMockConnector;
@@ -188,6 +196,19 @@
         }
 
         @Override
+        public void refreshSubscriptionsAndSendResult(
+                PendingIntent callbackIntent,
+                int resultCode,
+                Intent extrasIntent,
+                boolean isCallerAdmin,
+                String callingPackage,
+                int cardId,
+                Set<Integer> subscriptions) {
+            mCalledRefreshSubscriptionsAndSendResult = true;
+            sendResult(callbackIntent, resultCode, extrasIntent);
+        }
+
+        @Override
         public void sendOtaStatusChangedBroadcast() {
             ++mNumOtaStatusChanged;
         }
@@ -257,6 +278,73 @@
     }
 
     @Test(expected = SecurityException.class)
+    public void testGetAvailableMemoryInBytes_noPrivileges() throws Exception {
+        setGetAvailableMemoryInBytesPermissions(
+                false /* hasPhoneState */,
+                false /* hasPhoneStatePrivileged */,
+                false /* hasCarrierPrivileges */);
+        callGetAvailableMemoryInBytes(true /* success */, AVAILABLE_MEMORY, CARD_ID);
+    }
+
+    @Test
+    public void testGetAvailableMemoryInBytes_withPhoneState() throws Exception {
+        setGetAvailableMemoryInBytesPermissions(
+                true /* hasPhoneState */,
+                false /* hasPhoneStatePrivileged */,
+                false /* hasCarrierPrivileges */);
+        assertEquals(
+                AVAILABLE_MEMORY,
+                callGetAvailableMemoryInBytes(true /* success */, AVAILABLE_MEMORY, CARD_ID));
+    }
+
+    @Test
+    public void testGetAvailableMemoryInBytes_withPhoneStatePrivileged() throws Exception {
+        setGetAvailableMemoryInBytesPermissions(
+                false /* hasPhoneState */,
+                true /* hasPhoneStatePrivileged */,
+                false /* hasCarrierPrivileges */);
+        assertEquals(
+                AVAILABLE_MEMORY,
+                callGetAvailableMemoryInBytes(true /* success */, AVAILABLE_MEMORY, CARD_ID));
+    }
+
+    @Test
+    public void testGetAvailableMemoryInBytes_withCarrierPrivileges() throws Exception {
+        setGetAvailableMemoryInBytesPermissions(
+                false /* hasPhoneState */,
+                false /* hasPhoneStatePrivileged */,
+                true /* hasCarrierPrivileges */);
+        assertEquals(
+                AVAILABLE_MEMORY,
+                callGetAvailableMemoryInBytes(true /* success */, AVAILABLE_MEMORY, CARD_ID));
+    }
+
+    @Test
+    public void testGetAvailableMemoryInBytes_failure() throws Exception {
+        setGetAvailableMemoryInBytesPermissions(
+                true /* hasPhoneState */,
+                false /* hasPhoneStatePrivileged */,
+                false /* hasCarrierPrivileges */);
+        assertEquals(
+                EuiccManager.EUICC_MEMORY_FIELD_UNAVAILABLE,
+                callGetAvailableMemoryInBytes(false /* success */, AVAILABLE_MEMORY, CARD_ID));
+    }
+
+    @Test
+    public void testGetAvailableMemoryInBytes_unsupportedCardId() throws Exception {
+        setGetAvailableMemoryInBytesPermissions(
+                false /* hasPhoneState */,
+                false /* hasPhoneStatePrivileged */,
+                true /* hasCarrierPrivileges */);
+        assertEquals(
+                AVAILABLE_MEMORY,
+                callGetAvailableMemoryInBytes(
+                        true /* success */,
+                        AVAILABLE_MEMORY,
+                        TelephonyManager.UNSUPPORTED_CARD_ID));
+    }
+
+    @Test(expected = SecurityException.class)
     public void testGetOtaStatus_noPrivileges() {
         setHasWriteEmbeddedPermission(false /* hasPermission */);
         callGetOtaStatus(true /* success */, 1 /* status */);
@@ -748,6 +836,132 @@
     }
 
     @Test
+    @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
+    public void testDownloadSubscription_noAdminPermission()
+            throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasWriteEmbeddedPermission(false);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(false);
+        setUpUiccSlotData();
+        GetDownloadableSubscriptionMetadataResult result =
+                new GetDownloadableSubscriptionMetadataResult(
+                        EuiccService.RESULT_OK, SUBSCRIPTION_WITH_METADATA);
+        prepareGetDownloadableSubscriptionMetadataCall(true /* complete */, result);
+        PackageInfo pi = new PackageInfo();
+        pi.packageName = PACKAGE_NAME;
+        when(mPackageManager.getPackageInfo(eq(PACKAGE_NAME), anyInt())).thenReturn(pi);
+        setCanManageSubscriptionOnTargetSim(false /* isTargetEuicc */, false /* hasPrivileges */);
+
+        callDownloadSubscription(SUBSCRIPTION, false /* switchAfterDownload */, true /* complete */,
+                12345, 0 /* resolvableError */, PACKAGE_NAME /* callingPackage */);
+
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR,
+                0 /* detailedCode */);
+        verify(mMockConnector, never()).downloadSubscription(anyInt(), anyInt(),
+                any(), anyBoolean(), anyBoolean(), any(), any());
+    }
+
+    @Test
+    @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
+    public void testDownloadSubscription_adminPermission()
+            throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(true);
+        setHasWriteEmbeddedPermission(false);
+
+        callDownloadSubscription(SUBSCRIPTION, false /* switchAfterDownload */, true /* complete */,
+                EuiccService.RESULT_OK, 0 /* resolvableError */, "whatever" /* callingPackage */);
+
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK, 0 /* detailedCode */);
+        assertTrue(mController.mCalledRefreshSubscriptionsAndSendResult);
+    }
+
+    @Test
+    @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
+    public void testDownloadSubscription_adminPermission_usingSwitchAfterDownload()
+            throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasWriteEmbeddedPermission(false);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(true);
+        setUpUiccSlotData();
+        GetDownloadableSubscriptionMetadataResult result =
+                new GetDownloadableSubscriptionMetadataResult(
+                        EuiccService.RESULT_OK, SUBSCRIPTION_WITH_METADATA);
+        prepareGetDownloadableSubscriptionMetadataCall(true /* complete */, result);
+        PackageInfo pi = new PackageInfo();
+        pi.packageName = PACKAGE_NAME;
+        when(mPackageManager.getPackageInfo(eq(PACKAGE_NAME), anyInt())).thenReturn(pi);
+        setCanManageSubscriptionOnTargetSim(false /* isTargetEuicc */, false /* hasPrivileges */);
+
+        callDownloadSubscription(SUBSCRIPTION, true /* switchAfterDownload */, true /* complete */,
+                12345, 0 /* resolvableError */, PACKAGE_NAME /* callingPackage */);
+
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR,
+                0 /* detailedCode */);
+        verify(mMockConnector, never()).downloadSubscription(anyInt(), anyInt(),
+                any(), anyBoolean(), anyBoolean(), any(), any());
+    }
+
+    @Test
+    @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
+    public void testDownloadSubscription_onlyAdminManagedAllowed_callerNotAdmin_throws()
+            throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(false);
+        setHasWriteEmbeddedPermission(true);
+        doReturn(true)
+                .when(mUserManager)
+                .hasUserRestriction(UserManager.DISALLOW_SIM_GLOBALLY);
+
+        assertThrows(SecurityException.class,
+                () ->
+                        callDownloadSubscription(
+                                SUBSCRIPTION,
+                                false /* switchAfterDownload */,
+                                true /* complete */,
+                                EuiccService.RESULT_OK,
+                                0 /* resolvableError */,
+                                "whatever" /* callingPackage */));
+        assertFalse(mController.mCalledRefreshSubscriptionsAndSendResult);
+    }
+
+    @Test
+    @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
+    public void testDownloadSubscription_onlyAdminManagedAllowed_callerNotAdmin_disabled_success()
+            throws Exception {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(false);
+        setHasWriteEmbeddedPermission(true);
+        doReturn(true)
+                .when(mUserManager)
+                .hasUserRestriction(UserManager.DISALLOW_SIM_GLOBALLY);
+
+        callDownloadSubscription(SUBSCRIPTION, false /* switchAfterDownload */, true /* complete */,
+                EuiccService.RESULT_OK, 0 /* resolvableError */, "whatever" /* callingPackage */);
+
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK, 0 /* detailedCode */);
+        assertTrue(mController.mCalledRefreshSubscriptionsAndSendResult);
+    }
+
+    @Test
+    @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
+    public void testDownloadSubscription_onlyAdminManagedAllowed_callerIsAdmin_success()
+            throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(true);
+        setHasWriteEmbeddedPermission(false);
+        doReturn(true)
+                .when(mUserManager)
+                .hasUserRestriction(UserManager.DISALLOW_SIM_GLOBALLY);
+
+        callDownloadSubscription(SUBSCRIPTION, false /* switchAfterDownload */, true /* complete */,
+                EuiccService.RESULT_OK, 0 /* resolvableError */, "whatever" /* callingPackage */);
+
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK, 0 /* detailedCode */);
+        assertTrue(mController.mCalledRefreshSubscriptionsAndSendResult);
+    }
+
+    @Test
     public void testDeleteSubscription_noSuchSubscription() throws Exception {
         setHasWriteEmbeddedPermission(true);
         callDeleteSubscription(
@@ -791,6 +1005,82 @@
         assertTrue(mController.mCalledRefreshSubscriptionsAndSendResult);
     }
 
+
+    @Test
+    public void testDeleteSubscription_adminOwned_success() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasWriteEmbeddedPermission(false);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(true);
+        String callingPackage = "whatever";
+        SubscriptionInfo subInfo1 = new SubscriptionInfo.Builder()
+                .setId(SUBSCRIPTION_ID)
+                .setEmbedded(true)
+                .setIccId(ICC_ID)
+                .setCardId(CARD_ID)
+                .setPortIndex(TelephonyManager.DEFAULT_PORT_INDEX)
+                .setGroupOwner(callingPackage)
+                .build();
+        ArrayList<SubscriptionInfo> subInfos = new ArrayList<>(Arrays.asList(subInfo1));
+        when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(subInfos);
+
+        callDeleteSubscription(
+                SUBSCRIPTION_ID, ICC_ID, true /* complete */,
+                0 /* result */, callingPackage /* callingPackage */);
+
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK,
+                0 /* detailedCode */);
+    }
+
+    @Test
+    public void testDeleteSubscription_adminOwned_featureDisabled_success() throws Exception {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasWriteEmbeddedPermission(true);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(false);
+        String callingPackage = "whatever";
+        SubscriptionInfo subInfo1 = new SubscriptionInfo.Builder()
+                .setId(SUBSCRIPTION_ID)
+                .setEmbedded(true)
+                .setIccId(ICC_ID)
+                .setCardId(CARD_ID)
+                .setPortIndex(TelephonyManager.DEFAULT_PORT_INDEX)
+                .setGroupOwner(callingPackage)
+                .build();
+        ArrayList<SubscriptionInfo> subInfos = new ArrayList<>(Arrays.asList(subInfo1));
+        when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(subInfos);
+
+        callDeleteSubscription(
+                SUBSCRIPTION_ID, ICC_ID, true /* complete */,
+                0 /* result */, callingPackage /* callingPackage */);
+
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK,
+                0 /* detailedCode */);
+    }
+
+    @Test
+    public void testDeleteSubscription_adminOwned_noPermissions_error() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasWriteEmbeddedPermission(false);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(false);
+        String callingPackage = "whatever";
+        SubscriptionInfo subInfo1 = new SubscriptionInfo.Builder()
+                .setId(SUBSCRIPTION_ID)
+                .setEmbedded(true)
+                .setIccId(ICC_ID)
+                .setCardId(CARD_ID)
+                .setPortIndex(TelephonyManager.DEFAULT_PORT_INDEX)
+                .setGroupOwner(callingPackage)
+                .build();
+        ArrayList<SubscriptionInfo> subInfos = new ArrayList<>(Arrays.asList(subInfo1));
+        when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(subInfos);
+
+        callDeleteSubscription(
+                SUBSCRIPTION_ID, ICC_ID, true /* complete */,
+                0 /* result */, callingPackage /* callingPackage */);
+
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR,
+                0 /* detailedCode */);
+    }
+
     @Test
     public void testGetResolvedPortIndexForSubscriptionSwitchWithOutMEP() throws Exception {
         setUpUiccSlotData();
@@ -1495,6 +1785,25 @@
         setHasCarrierPrivilegesOnActiveSubscription(hasCarrierPrivileges);
     }
 
+    private void setGetAvailableMemoryInBytesPermissions(
+            boolean hasPhoneState, boolean hasPhoneStatePrivileged, boolean hasCarrierPrivileges)
+            throws Exception {
+        doReturn(
+                        hasPhoneState
+                                ? PackageManager.PERMISSION_GRANTED
+                                : PackageManager.PERMISSION_DENIED)
+                .when(mContext)
+                .checkCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
+        doReturn(
+                        hasPhoneStatePrivileged
+                                ? PackageManager.PERMISSION_GRANTED
+                                : PackageManager.PERMISSION_DENIED)
+                .when(mContext)
+                .checkCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+        when(mTelephonyManager.getPhoneCount()).thenReturn(1);
+        setHasCarrierPrivilegesOnActiveSubscription(hasCarrierPrivileges);
+    }
+
     private void setHasWriteEmbeddedPermission(boolean hasPermission) {
         doReturn(hasPermission
                 ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED)
@@ -1502,6 +1811,15 @@
                 .checkCallingOrSelfPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS);
     }
 
+    private void setHasManageDevicePolicyManagedSubscriptionsPermission(boolean hasPermission) {
+        doReturn(hasPermission
+                ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED)
+                .when(mContext)
+                .checkCallingOrSelfPermission(
+                        Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS);
+    }
+
+
     private void setHasMasterClearPermission(boolean hasPermission) {
         Stubber stubber = hasPermission ? doNothing() : doThrow(new SecurityException());
         stubber.when(mContext).enforceCallingPermission(
@@ -1618,6 +1936,29 @@
         return mController.getEid(cardId, PACKAGE_NAME);
     }
 
+    private long callGetAvailableMemoryInBytes(
+            final boolean success, final long availableMemoryInBytes, int cardId) {
+        doAnswer(
+                new Answer<Void>() {
+                    @Override
+                    public Void answer(InvocationOnMock invocation) throws Exception {
+                        EuiccConnector.GetAvailableMemoryInBytesCommandCallback cb =
+                                invocation.getArgument(1 /* resultCallback */);
+                        if (success) {
+                            cb.onGetAvailableMemoryInBytesComplete(availableMemoryInBytes);
+                        } else {
+                            cb.onEuiccServiceUnavailable();
+                        }
+                        return null;
+                    }
+                })
+                .when(mMockConnector)
+                .getAvailableMemoryInBytes(
+                        anyInt(),
+                        Mockito.<EuiccConnector.GetAvailableMemoryInBytesCommandCallback>any());
+        return mController.getAvailableMemoryInBytes(cardId, PACKAGE_NAME);
+    }
+
     private int callGetOtaStatus(final boolean success, final int status) {
         doAnswer(new Answer<Void>() {
             @Override
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 6496efb..14cff4b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
@@ -1603,6 +1603,17 @@
         assertEquals(2, copiedDialArgs.eccCategory);
     }
 
+    @Test
+    @SmallTest
+    public void testCanMakeWifiCall() {
+        mImsPhoneUT.setServiceState(ServiceState.STATE_IN_SERVICE);
+        mImsPhoneUT.setImsRegistered(true);
+        doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN).when(mImsCT)
+                .getImsRegistrationTech();
+
+        assertTrue(mImsPhoneUT.canMakeWifiCall());
+    }
+
     private ServiceState getServiceStateDataAndVoice(int rat, int regState, boolean isRoaming) {
         ServiceState ss = new ServiceState();
         ss.setStateOutOfService();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
index c992f4d..475c90b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
@@ -160,6 +160,8 @@
     private CellularServiceState[] mServiceStates;
 
     // IMS registrations for slot 0 and 1
+    private ImsRegistrationStats mImsRegStatsUnregisteredLte0;
+    private ImsRegistrationStats mImsRegStatsRegisteringLte0;
     private ImsRegistrationStats mImsRegistrationStatsLte0;
     private ImsRegistrationStats mImsRegistrationStatsWifi0;
     private ImsRegistrationStats mImsRegistrationStatsLte1;
@@ -504,6 +506,42 @@
                     mServiceState5Proto
                 };
 
+        // IMS over LTE on slot 0, unregistered for 5 seconds at the registered state
+        mImsRegStatsUnregisteredLte0 = new ImsRegistrationStats();
+        mImsRegStatsUnregisteredLte0.carrierId = CARRIER1_ID;
+        mImsRegStatsUnregisteredLte0.simSlotIndex = 0;
+        mImsRegStatsUnregisteredLte0.rat = TelephonyManager.NETWORK_TYPE_LTE;
+        mImsRegStatsUnregisteredLte0.registeredMillis = 0;
+        mImsRegStatsUnregisteredLte0.voiceCapableMillis = 5000L;
+        mImsRegStatsUnregisteredLte0.voiceAvailableMillis = 5000L;
+        mImsRegStatsUnregisteredLte0.smsCapableMillis = 5000L;
+        mImsRegStatsUnregisteredLte0.smsAvailableMillis = 5000L;
+        mImsRegStatsUnregisteredLte0.videoCapableMillis = 5000L;
+        mImsRegStatsUnregisteredLte0.videoAvailableMillis = 5000L;
+        mImsRegStatsUnregisteredLte0.utCapableMillis = 5000L;
+        mImsRegStatsUnregisteredLte0.utAvailableMillis = 5000L;
+        mImsRegStatsUnregisteredLte0.registeringMillis = 0;
+        mImsRegStatsUnregisteredLte0.unregisteredMillis = 5000L;
+        mImsRegStatsUnregisteredLte0.registeredTimes = 0;
+
+        // IMS over LTE on slot 0, registering for 5 seconds at the registered state
+        mImsRegStatsRegisteringLte0 = new ImsRegistrationStats();
+        mImsRegStatsRegisteringLte0.carrierId = CARRIER1_ID;
+        mImsRegStatsRegisteringLte0.simSlotIndex = 0;
+        mImsRegStatsRegisteringLte0.rat = TelephonyManager.NETWORK_TYPE_LTE;
+        mImsRegStatsRegisteringLte0.registeredMillis = 0;
+        mImsRegStatsRegisteringLte0.voiceCapableMillis = 5000L;
+        mImsRegStatsRegisteringLte0.voiceAvailableMillis = 5000L;
+        mImsRegStatsRegisteringLte0.smsCapableMillis = 5000L;
+        mImsRegStatsRegisteringLte0.smsAvailableMillis = 5000L;
+        mImsRegStatsRegisteringLte0.videoCapableMillis = 5000L;
+        mImsRegStatsRegisteringLte0.videoAvailableMillis = 5000L;
+        mImsRegStatsRegisteringLte0.utCapableMillis = 5000L;
+        mImsRegStatsRegisteringLte0.utAvailableMillis = 5000L;
+        mImsRegStatsRegisteringLte0.registeringMillis = 5000L;
+        mImsRegStatsRegisteringLte0.unregisteredMillis = 0;
+        mImsRegStatsRegisteringLte0.registeredTimes = 1;
+
         // IMS over LTE on slot 0, registered for 5 seconds
         mImsRegistrationStatsLte0 = new ImsRegistrationStats();
         mImsRegistrationStatsLte0.carrierId = CARRIER1_ID;
@@ -518,6 +556,9 @@
         mImsRegistrationStatsLte0.videoAvailableMillis = 5000L;
         mImsRegistrationStatsLte0.utCapableMillis = 5000L;
         mImsRegistrationStatsLte0.utAvailableMillis = 5000L;
+        mImsRegistrationStatsLte0.registeringMillis = 0;
+        mImsRegistrationStatsLte0.unregisteredMillis = 0;
+        mImsRegistrationStatsLte0.registeredTimes = 0;
 
         // IMS over WiFi on slot 0, registered for 10 seconds (voice only)
         mImsRegistrationStatsWifi0 = new ImsRegistrationStats();
@@ -542,6 +583,9 @@
         mImsRegistrationStatsLte1.videoAvailableMillis = 20000L;
         mImsRegistrationStatsLte1.utCapableMillis = 20000L;
         mImsRegistrationStatsLte1.utAvailableMillis = 20000L;
+        mImsRegistrationStatsLte1.registeringMillis = 0;
+        mImsRegistrationStatsLte1.unregisteredMillis = 0;
+        mImsRegistrationStatsLte1.registeredTimes = 0;
 
         // IMS terminations on LTE
         mImsRegistrationTerminationLte = new ImsRegistrationTermination();
@@ -567,8 +611,13 @@
 
         mImsRegistrationStats =
                 new ImsRegistrationStats[] {
-                    mImsRegistrationStatsLte0, mImsRegistrationStatsWifi0, mImsRegistrationStatsLte1
+                    mImsRegStatsUnregisteredLte0,
+                    mImsRegStatsRegisteringLte0,
+                    mImsRegistrationStatsLte0,
+                    mImsRegistrationStatsWifi0,
+                    mImsRegistrationStatsLte1
                 };
+
         mImsRegistrationTerminations =
                 new ImsRegistrationTermination[] {
                     mImsRegistrationTerminationLte, mImsRegistrationTerminationWifi
@@ -1144,6 +1193,8 @@
         mServiceState5Proto = null;
         mServiceSwitches = null;
         mServiceStates = null;
+        mImsRegStatsUnregisteredLte0 = null;
+        mImsRegStatsRegisteringLte0 = null;
         mImsRegistrationStatsLte0 = null;
         mImsRegistrationStatsWifi0 = null;
         mImsRegistrationStatsLte1 = null;
@@ -1753,7 +1804,7 @@
         mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegistrationStatsLte0));
         mPersistAtomsStorage.incTimeMillis(DAY_IN_MILLIS);
 
-        // Service state and service switch should be added successfully
+        // mImsRegistrationStatsLte0 should be added successfully
         verifyCurrentStateSavedToFileOnce();
         ImsRegistrationStats[] regStats = mPersistAtomsStorage.getImsRegistrationStats(0L);
         assertProtoArrayEquals(new ImsRegistrationStats[] {mImsRegistrationStatsLte0}, regStats);
@@ -1769,7 +1820,7 @@
         mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegistrationStatsWifi0));
         mPersistAtomsStorage.incTimeMillis(DAY_IN_MILLIS);
 
-        // Service state and service switch should be added successfully
+        // mImsRegistrationStatsLte0 and mImsRegistrationStatsWifi0 should be added successfully
         verifyCurrentStateSavedToFileOnce();
         ImsRegistrationStats[] regStats = mPersistAtomsStorage.getImsRegistrationStats(0L);
         assertProtoArrayEqualsIgnoringOrder(
@@ -1779,18 +1830,34 @@
 
     @Test
     @SmallTest
-    public void addImsRegistrationStats_updateExistingEntries() throws Exception {
-        createTestFile(START_TIME_MILLIS);
-        ImsRegistrationStats newImsRegistrationStatsLte0 = copyOf(mImsRegistrationStatsLte0);
+    public void addImsRegistrationStats_withExistingRegisteringEntries() throws Exception {
+        createEmptyTestFile();
         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
-
-        mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegistrationStatsLte0));
+        mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegStatsRegisteringLte0));
         mPersistAtomsStorage.incTimeMillis(DAY_IN_MILLIS);
 
-        // mImsRegistrationStatsLte0's durations should be doubled
+        // mImsRegStatsRegisteringLte0's info should be added successfully
         verifyCurrentStateSavedToFileOnce();
-        ImsRegistrationStats[] serviceStates = mPersistAtomsStorage.getImsRegistrationStats(0L);
-        newImsRegistrationStatsLte0.registeredMillis *= 2;
+        ImsRegistrationStats[] regStats = mPersistAtomsStorage.getImsRegistrationStats(0L);
+        assertProtoArrayEqualsIgnoringOrder(
+                new ImsRegistrationStats[] {mImsRegStatsRegisteringLte0},
+                regStats);
+    }
+
+    @Test
+    @SmallTest
+    public void addImsRegistrationStats_updateExistingEntries() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+        ImsRegistrationStats newImsRegistrationStatsLte0 = copyOf(mImsRegStatsUnregisteredLte0);
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegStatsUnregisteredLte0));
+        mPersistAtomsStorage.incTimeMillis(DAY_IN_MILLIS);
+
+        // mImsRegStatsUnregisteredLte0's durations should be doubled
+        verifyCurrentStateSavedToFileOnce();
+        ImsRegistrationStats[] regStats = mPersistAtomsStorage.getImsRegistrationStats(0L);
+        newImsRegistrationStatsLte0.unregisteredMillis *= 2;
         newImsRegistrationStatsLte0.voiceCapableMillis *= 2;
         newImsRegistrationStatsLte0.voiceAvailableMillis *= 2;
         newImsRegistrationStatsLte0.smsCapableMillis *= 2;
@@ -1802,10 +1869,12 @@
         assertProtoArrayEqualsIgnoringOrder(
                 new ImsRegistrationStats[] {
                     newImsRegistrationStatsLte0,
+                    mImsRegStatsRegisteringLte0,
+                    mImsRegistrationStatsLte0,
                     mImsRegistrationStatsWifi0,
                     mImsRegistrationStatsLte1
                 },
-                serviceStates);
+                regStats);
     }
 
     @Test
@@ -1834,6 +1903,39 @@
 
     @Test
     @SmallTest
+    public void addImsRegistrationStats_withExistingDurationEntries() throws Exception {
+        createEmptyTestFile();
+        ImsRegistrationStats newImsRegStatsLte0 = copyOf(mImsRegistrationStatsLte0);
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegStatsUnregisteredLte0));
+        mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegStatsRegisteringLte0));
+        mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegistrationStatsLte0));
+        mPersistAtomsStorage.incTimeMillis(DAY_IN_MILLIS);
+
+        // UnregisteredMillis, registeringMillis and registeredTimes should be added successfully
+        // capable and available durations should be tripled
+        verifyCurrentStateSavedToFileOnce();
+        ImsRegistrationStats[] regStats = mPersistAtomsStorage.getImsRegistrationStats(0L);
+        newImsRegStatsLte0.unregisteredMillis += newImsRegStatsLte0.registeredMillis;
+        newImsRegStatsLte0.registeringMillis += newImsRegStatsLte0.registeredMillis;
+        newImsRegStatsLte0.registeredTimes += 1;
+        newImsRegStatsLte0.voiceCapableMillis *= 3;
+        newImsRegStatsLte0.voiceAvailableMillis *= 3;
+        newImsRegStatsLte0.smsCapableMillis *= 3;
+        newImsRegStatsLte0.smsAvailableMillis *= 3;
+        newImsRegStatsLte0.videoCapableMillis *= 3;
+        newImsRegStatsLte0.videoAvailableMillis *= 3;
+        newImsRegStatsLte0.utCapableMillis *= 3;
+        newImsRegStatsLte0.utAvailableMillis *= 3;
+        assertProtoArrayEqualsIgnoringOrder(
+                new ImsRegistrationStats[] {
+                    newImsRegStatsLte0
+                },
+                regStats);
+    }
+
+    @Test
+    @SmallTest
     public void addImsRegistrationTermination_emptyProto() throws Exception {
         createEmptyTestFile();
 
@@ -1943,7 +2045,11 @@
         // pull timestamp should be updated and saved
         assertProtoArrayEqualsIgnoringOrder(
                 new ImsRegistrationStats[] {
-                    mImsRegistrationStatsLte0, mImsRegistrationStatsWifi0, mImsRegistrationStatsLte1
+                    mImsRegStatsUnregisteredLte0,
+                    mImsRegStatsRegisteringLte0,
+                    mImsRegistrationStatsLte0,
+                    mImsRegistrationStatsWifi0,
+                    mImsRegistrationStatsLte1
                 },
                 stats1);
         assertProtoArrayEquals(new ImsRegistrationStats[0], stats2);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
index fd5f6ab..37ff69a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT;
+
 import static com.android.internal.telephony.satellite.DatagramController.SATELLITE_ALIGN_TIMEOUT;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -29,6 +31,7 @@
 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;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
@@ -82,6 +85,10 @@
     private static final int DATAGRAM_TYPE2 = SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING;
     private static final String TEST_MESSAGE = "This is a test datagram message";
     private static final long TEST_EXPIRE_TIMER_SATELLITE_ALIGN = TimeUnit.SECONDS.toMillis(1);
+    private static final int TEST_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMEOUT_MILLIS =
+            (int) TimeUnit.SECONDS.toMillis(180);
+    private static final long TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS =
+            TimeUnit.SECONDS.toMillis(60);
 
     private DatagramDispatcher mDatagramDispatcherUT;
     private TestDatagramDispatcher mTestDemoModeDatagramDispatcher;
@@ -155,7 +162,7 @@
     }
 
     @Test
-    public void testSendSatelliteDatagram_usingSatelliteModemInterface_success() throws  Exception {
+    public void testSendSatelliteDatagram_success() throws  Exception {
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[3];
 
@@ -168,7 +175,7 @@
         doReturn(true).when(mMockDatagramController)
                 .needsWaitingForSatelliteConnected();
         when(mMockDatagramController.getDatagramWaitTimeForConnectedState())
-                .thenReturn(DatagramController.DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT);
+                .thenReturn(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
         mResultListener.clear();
 
         mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
@@ -221,7 +228,7 @@
         mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState();
         assertTrue(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
 
-        moveTimeForward(DatagramController.DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT);
+        moveTimeForward(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
         processAllMessages();
         verifyZeroInteractions(mMockSatelliteModemInterface);
         mInOrder.verify(mMockDatagramController)
@@ -267,7 +274,87 @@
     }
 
     @Test
-    public void testSendSatelliteDatagram_usingSatelliteModemInterface_failure() throws  Exception {
+    public void testSendSatelliteDatagram_timeout() throws  Exception {
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[3];
+
+            mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
+                            new AsyncResult(message.obj, null, null))
+                    .sendToTarget();
+
+            // DatagramDispatcher should ignore the second EVENT_SEND_SATELLITE_DATAGRAM_DONE
+            mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
+                            new AsyncResult(message.obj, null, null))
+                    .sendToTarget();
+
+            return null;
+        }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(any(SatelliteDatagram.class),
+                anyBoolean(), anyBoolean(), any(Message.class));
+        doReturn(false).when(mMockDatagramController)
+                .needsWaitingForSatelliteConnected();
+        when(mMockDatagramController.getDatagramWaitTimeForConnectedState())
+                .thenReturn(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
+        mContextFixture.putIntResource(
+                R.integer.config_wait_for_datagram_sending_response_timeout_millis,
+                TEST_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMEOUT_MILLIS);
+        mResultListener.clear();
+
+        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
+                true, mResultListener::offer);
+        processAllMessages();
+        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
+        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS), eq(0),
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+        verifyNoMoreInteractions(mMockDatagramController);
+        verify(mMockSatelliteModemInterface, times(1)).sendSatelliteDatagram(
+                any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_RESULT_SUCCESS);
+
+        clearInvocations(mMockSatelliteModemInterface);
+        clearInvocations(mMockDatagramController);
+        mResultListener.clear();
+
+        // No response for the send request from modem
+        doNothing().when(mMockSatelliteModemInterface).sendSatelliteDatagram(
+                any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+
+        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
+                true, mResultListener::offer);
+        processAllMessages();
+        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
+        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED), eq(1),
+                        eq(SATELLITE_RESULT_MODEM_TIMEOUT));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+        verifyNoMoreInteractions(mMockDatagramController);
+        verify(mMockSatelliteModemInterface, times(1)).sendSatelliteDatagram(
+                any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+        verify(mMockSatelliteModemInterface).abortSendingSatelliteDatagrams(any(Message.class));
+        assertThat(mResultListener.peek()).isEqualTo(SATELLITE_RESULT_MODEM_TIMEOUT);
+    }
+
+    @Test
+    public void testSendSatelliteDatagram_failure() throws  Exception {
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[3];
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
index 0e16e25..3a42881 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
@@ -78,6 +78,8 @@
     private static final int SUB_ID = 0;
     private static final String TEST_MESSAGE = "This is a test datagram message";
     private static final long TEST_EXPIRE_TIMER_SATELLITE_ALIGN = TimeUnit.SECONDS.toMillis(1);
+    private static final long TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS =
+            TimeUnit.SECONDS.toMillis(60);
 
     private DatagramReceiver mDatagramReceiverUT;
     private DatagramReceiver.SatelliteDatagramListenerHandler mSatelliteDatagramListenerHandler;
@@ -163,7 +165,7 @@
         }).when(mMockSatelliteModemInterface).pollPendingSatelliteDatagrams(any(Message.class));
         doReturn(true).when(mMockDatagramController).needsWaitingForSatelliteConnected();
         when(mMockDatagramController.getDatagramWaitTimeForConnectedState())
-                .thenReturn(DatagramController.DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT);
+                .thenReturn(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
         mResultListener.clear();
 
         mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
@@ -201,7 +203,7 @@
         verifyZeroInteractions(mMockSatelliteModemInterface);
         assertTrue(mDatagramReceiverUT.isDatagramWaitForConnectedStateTimerStarted());
 
-        moveTimeForward(DatagramController.DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT);
+        moveTimeForward(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
         processAllMessages();
         mInOrder.verify(mMockDatagramController).updateReceiveStatus(eq(SUB_ID),
                 eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED), eq(0),
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 f7483d9..431b4cc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
@@ -18,6 +18,7 @@
 
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL;
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT;
+import static android.telephony.SubscriptionManager.SATELLITE_ENTITLEMENT_STATUS;
 import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_GOOD;
 import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_GREAT;
 import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE;
@@ -45,6 +46,7 @@
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_INVALID_MODEM_STATE;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_ERROR;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NOT_AUTHORIZED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NO_RESOURCES;
@@ -83,6 +85,7 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
+import android.app.NotificationManager;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.os.AsyncResult;
@@ -131,6 +134,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
@@ -159,7 +163,11 @@
     private static final String TEST_NEXT_SATELLITE_TOKEN = "TEST_NEXT_SATELLITE_TOKEN";
     private static final String[] EMPTY_STRING_ARRAY = {};
     private static final List<String> EMPTY_STRING_LIST = new ArrayList<>();
+    private static final String SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY =
+            "satellite_system_notification_done_key";
     private static final int[] ACTIVE_SUB_IDS = {SUB_ID};
+    private static final int TEST_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMEOUT_MILLIS =
+            (int) TimeUnit.SECONDS.toMillis(60);
     private List<Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener>>
             mCarrierConfigChangedListenerList = new ArrayList<>();
 
@@ -176,6 +184,7 @@
     @Mock private ProvisionMetricsStats mMockProvisionMetricsStats;
     @Mock private SessionMetricsStats mMockSessionMetricsStats;
     @Mock private SubscriptionManagerService mMockSubscriptionManagerService;
+    @Mock private NotificationManager mMockNotificationManager;
     private List<Integer> mIIntegerConsumerResults =  new ArrayList<>();
     @Mock private ISatelliteTransmissionUpdateCallback mStartTransmissionUpdateCallback;
     @Mock private ISatelliteTransmissionUpdateCallback mStopTransmissionUpdateCallback;
@@ -460,6 +469,9 @@
         mContextFixture.putStringArrayResource(
                 R.array.config_satellite_providers,
                 EMPTY_STRING_ARRAY);
+        mContextFixture.putIntResource(
+                R.integer.config_wait_for_satellite_enabling_response_timeout_millis,
+                TEST_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMEOUT_MILLIS);
         doReturn(ACTIVE_SUB_IDS).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
 
         mCarrierConfigBundle = mContextFixture.getCarrierConfigBundle();
@@ -503,6 +515,13 @@
         doNothing().when(mMockProvisionMetricsStats).reportProvisionMetrics();
         doNothing().when(mMockControllerMetricsStats).reportDeprovisionCount(anyInt());
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        doReturn(mSST).when(mPhone).getServiceStateTracker();
+        doReturn(mSST).when(mPhone2).getServiceStateTracker();
+        doReturn(mServiceState).when(mSST).getServiceState();
+        doReturn(Context.NOTIFICATION_SERVICE).when(mContext).getSystemServiceName(
+                NotificationManager.class);
+        doReturn(mMockNotificationManager).when(mContext).getSystemService(
+                Context.NOTIFICATION_SERVICE);
         mSatelliteControllerUT =
                 new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
         verify(mMockSatelliteModemInterface).registerForSatelliteProvisionStateChanged(
@@ -2869,6 +2888,241 @@
         assertEquals(true, satelliteEnabledPerCarrier.get(SUB_ID1));
     }
 
+    @Test
+    public void testUpdateRestrictReasonForEntitlementPerCarrier() throws Exception {
+        logd("testUpdateRestrictReasonForEntitlementPerCarrier");
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+
+        // Verify that the entitlement restriction reason is added before the entitlement query,
+        // When the Satellite entitlement status value read from DB is disabled.
+        doReturn("").when(mContext).getOpPackageName();
+        doReturn("").when(mContext).getAttributionTag();
+        doReturn("0").when(mMockSubscriptionManagerService).getSubscriptionProperty(anyInt(),
+                eq(SATELLITE_ENTITLEMENT_STATUS), anyString(), anyString());
+        doReturn(new ArrayList<>()).when(
+                mMockSubscriptionManagerService).getSatelliteEntitlementPlmnList(anyInt());
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
+        mCarrierConfigBundle.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+        Set<Integer> restrictionSet =
+                mSatelliteControllerUT.getAttachRestrictionReasonsForCarrier(SUB_ID);
+        assertEquals(1, restrictionSet.size());
+        assertTrue(restrictionSet.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT));
+    }
+
+    @Test
+    public void testUpdateEntitlementPlmnListPerCarrier() throws Exception {
+        logd("testUpdateEntitlementPlmnListPerCarrier");
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+
+        // If the Satellite entitlement plmn list read from the DB is empty and carrier config
+        // plmn list also is empty , check whether an empty list is returned when calling
+        // getSatellitePlmnsForCarrier before the entitlement query.
+        doReturn(new ArrayList<>()).when(
+                mMockSubscriptionManagerService).getSatelliteEntitlementPlmnList(anyInt());
+        replaceInstance(SatelliteController.class, "mEntitlementPlmnListPerCarrier",
+                mSatelliteControllerUT, new SparseArray<>());
+        replaceInstance(SatelliteController.class, "mSatelliteServicesSupportedByCarriers",
+                mSatelliteControllerUT, new HashMap<>());
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
+        mCarrierConfigBundle.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+
+        assertEquals(new ArrayList<>(), mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID));
+
+        // If the Satellite entitlement plmn list read from the DB is valid and carrier config
+        // plmn list is empty, check whether valid entitlement plmn list is returned
+        // when calling getSatellitePlmnsForCarrier before the entitlement query.
+        replaceInstance(SatelliteController.class, "mEntitlementPlmnListPerCarrier",
+                mSatelliteControllerUT, new SparseArray<>());
+        List<String> expectedSatelliteEntitlementPlmnList = Arrays.asList("123456,12560");
+        doReturn(expectedSatelliteEntitlementPlmnList).when(
+                mMockSubscriptionManagerService).getSatelliteEntitlementPlmnList(anyInt());
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+
+        assertEquals(expectedSatelliteEntitlementPlmnList,
+                mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID));
+
+        // If the Satellite entitlement plmn list read from the DB is valid and carrier config
+        // plmn list is valid, check whether valid entitlement plmn list is returned when
+        // calling getSatellitePlmnsForCarrier before the entitlement query.
+        replaceInstance(SatelliteController.class, "mEntitlementPlmnListPerCarrier",
+                mSatelliteControllerUT, new SparseArray<>());
+        PersistableBundle carrierSupportedSatelliteServicesPerProvider = new PersistableBundle();
+        List<String> carrierConfigPlmnList = Arrays.asList("00102", "00103", "00105");
+        carrierSupportedSatelliteServicesPerProvider.putIntArray(
+                carrierConfigPlmnList.get(0), new int[]{2});
+        carrierSupportedSatelliteServicesPerProvider.putIntArray(
+                carrierConfigPlmnList.get(1), new int[]{1, 3});
+        carrierSupportedSatelliteServicesPerProvider.putIntArray(
+                carrierConfigPlmnList.get(2), new int[]{2});
+        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();
+
+        assertEquals(expectedSatelliteEntitlementPlmnList,
+                mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID));
+
+        // If the Satellite entitlement plmn list read from the DB is empty and carrier config
+        // plmn list is valid, check whether valid carrier config plmn list is returned when
+        // calling getSatellitePlmnsForCarrier before the entitlement query.
+        replaceInstance(SatelliteController.class, "mEntitlementPlmnListPerCarrier",
+                mSatelliteControllerUT, new SparseArray<>());
+        doReturn(new ArrayList<>()).when(
+                mMockSubscriptionManagerService).getSatelliteEntitlementPlmnList(anyInt());
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+
+        assertEquals(carrierConfigPlmnList.stream().sorted().toList(),
+                mSatelliteControllerUT.getSatellitePlmnsForCarrier(
+                        SUB_ID).stream().sorted().toList());
+    }
+
+    @Test
+    public void testHandleEventServiceStateChanged() throws Exception {
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        // Do nothing when the satellite is not connected
+        doReturn(false).when(mServiceState).isUsingNonTerrestrialNetwork();
+        sendServiceStateChangedEvent();
+        processAllMessages();
+        assertEquals(false,
+                mSharedPreferences.getBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY, false));
+        verify(mMockNotificationManager, never()).notifyAsUser(anyString(), anyInt(), any(), any());
+
+        // Check sending a system notification when the satellite is connected
+        doReturn(true).when(mServiceState).isUsingNonTerrestrialNetwork();
+        sendServiceStateChangedEvent();
+        processAllMessages();
+        verify(mMockNotificationManager, times(1)).notifyAsUser(anyString(), anyInt(), any(),
+                any());
+        assertEquals(true,
+                mSharedPreferences.getBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY, false));
+
+        // Check don't display again after displayed already a system notification.
+        sendServiceStateChangedEvent();
+        processAllMessages();
+        verify(mMockNotificationManager, times(1)).notifyAsUser(anyString(), anyInt(), any(),
+                any());
+    }
+
+    @Test
+    public void testRequestSatelliteEnabled_timeout() {
+        mIsSatelliteEnabledSemaphore.drainPermits();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        sendProvisionedStateChangedEvent(true, null);
+        processAllMessages();
+        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+
+        // Successfully disable satellite
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+
+        // Time out to enable satellite
+        ArgumentCaptor<Message> enableSatelliteResponse = ArgumentCaptor.forClass(Message.class);
+        mIIntegerConsumerResults.clear();
+        setUpNoResponseForRequestSatelliteEnabled(true, false);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        processAllMessages();
+        assertFalse(waitForIIntegerConsumerResult(1));
+        verify(mMockSatelliteModemInterface).requestSatelliteEnabled(eq(true), eq(false),
+                enableSatelliteResponse.capture());
+
+        clearInvocations(mMockSatelliteModemInterface);
+        moveTimeForward(TEST_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMEOUT_MILLIS);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_MODEM_TIMEOUT, (long) mIIntegerConsumerResults.get(0));
+        verify(mMockSatelliteModemInterface).requestSatelliteEnabled(eq(false), eq(false), any(
+                Message.class));
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+
+        // Send the response for the above request to enable satellite. SatelliteController should
+        // ignore the event
+        Message response = enableSatelliteResponse.getValue();
+        AsyncResult.forMessage(response, null, null);
+        response.sendToTarget();
+        processAllMessages();
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+
+        // Successfully enable satellite
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
+
+        // Time out to disable satellite
+        ArgumentCaptor<Message> disableSatelliteResponse = ArgumentCaptor.forClass(Message.class);
+        mIIntegerConsumerResults.clear();
+        clearInvocations(mMockSatelliteModemInterface);
+        setUpNoResponseForRequestSatelliteEnabled(false, false);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
+        processAllMessages();
+        assertFalse(waitForIIntegerConsumerResult(1));
+        verify(mMockSatelliteModemInterface).requestSatelliteEnabled(eq(false), eq(false),
+                disableSatelliteResponse.capture());
+
+        clearInvocations(mMockSatelliteModemInterface);
+        moveTimeForward(TEST_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMEOUT_MILLIS);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_MODEM_TIMEOUT, (long) mIIntegerConsumerResults.get(0));
+        verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(anyBoolean(),
+                anyBoolean(), any(Message.class));
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+
+        // Send the response for the above request to disable satellite. SatelliteController should
+        // ignore the event
+        response = disableSatelliteResponse.getValue();
+        AsyncResult.forMessage(response, null, null);
+        response.sendToTarget();
+        processAllMessages();
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+    }
+
     private void resetSatelliteControllerUTEnabledState() {
         logd("resetSatelliteControllerUTEnabledState");
         setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
index 6d3bb4e..a1c2cfc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
@@ -18,6 +18,7 @@
 
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED;
@@ -571,6 +572,35 @@
                 SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
         clearInvocations(mMockDatagramController);
 
+        // Start receiving datagrams
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS);
+        processAllMessages();
+
+        // SatelliteSessionController should move to TRANSFERRING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        clearInvocations(mMockDatagramController);
+
+        // Receiving datagrams is successful and done.
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        processAllMessages();
+
+        // SatelliteSessionController should move to CONNECTED state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
         // Wait for timeout
         moveTimeForward(TEST_SATELLITE_TIMEOUT_MILLIS);
         processAllMessages();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifierTest.java b/tests/telephonytests/src/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifierTest.java
index 582c658..8841c7a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifierTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifierTest.java
@@ -20,13 +20,24 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+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.content.Context;
 import android.telephony.CellularIdentifierDisclosure;
 
 import com.android.internal.telephony.TestExecutorService;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.InOrder;
 
 import java.util.concurrent.TimeUnit;
 
@@ -38,6 +49,9 @@
     private static final int SUB_ID_1 = 1;
     private static final int SUB_ID_2 = 2;
     private CellularIdentifierDisclosure mDislosure;
+    private CellularNetworkSecuritySafetySource mSafetySource;
+    private Context mContext;
+    private InOrder mInOrder;
 
     @Before
     public void setUp() {
@@ -47,33 +61,41 @@
                         CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI,
                         "001001",
                         false);
+        mSafetySource = mock(CellularNetworkSecuritySafetySource.class);
+        mInOrder = inOrder(mSafetySource);
     }
 
     @Test
     public void testInitializeDisabled() {
         TestExecutorService executor = new TestExecutorService();
         CellularIdentifierDisclosureNotifier notifier =
-                new CellularIdentifierDisclosureNotifier(executor, 15, TimeUnit.MINUTES);
+                new CellularIdentifierDisclosureNotifier(
+                        executor, 15, TimeUnit.MINUTES, mSafetySource);
 
         assertFalse(notifier.isEnabled());
+        verify(mSafetySource, never()).setIdentifierDisclosureIssueEnabled(any(), anyBoolean());
     }
 
     @Test
     public void testDisableAddDisclosureNop() {
         TestExecutorService executor = new TestExecutorService();
         CellularIdentifierDisclosureNotifier notifier =
-                new CellularIdentifierDisclosureNotifier(executor, 15, TimeUnit.MINUTES);
+                new CellularIdentifierDisclosureNotifier(
+                        executor, 15, TimeUnit.MINUTES, mSafetySource);
 
         assertFalse(notifier.isEnabled());
-        notifier.addDisclosure(SUB_ID_1, mDislosure);
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
         assertEquals(0, notifier.getCurrentDisclosureCount(SUB_ID_1));
+        verify(mSafetySource, never())
+                .setIdentifierDisclosure(any(), anyInt(), anyInt(), any(), any());
     }
 
     @Test
     public void testAddDisclosureEmergencyNop() {
         TestExecutorService executor = new TestExecutorService();
         CellularIdentifierDisclosureNotifier notifier =
-                new CellularIdentifierDisclosureNotifier(executor, 15, TimeUnit.MINUTES);
+                new CellularIdentifierDisclosureNotifier(
+                        executor, 15, TimeUnit.MINUTES, mSafetySource);
         CellularIdentifierDisclosure emergencyDisclosure =
                 new CellularIdentifierDisclosure(
                         CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
@@ -81,116 +103,161 @@
                         "001001",
                         true);
 
-        notifier.enable();
-        notifier.addDisclosure(SUB_ID_1, emergencyDisclosure);
+        notifier.enable(mContext);
+        notifier.addDisclosure(mContext, SUB_ID_1, emergencyDisclosure);
 
         assertEquals(0, notifier.getCurrentDisclosureCount(SUB_ID_1));
+        verify(mSafetySource, never())
+                .setIdentifierDisclosure(any(), anyInt(), anyInt(), any(), any());
     }
 
     @Test
     public void testAddDisclosureCountIncrements() {
         TestExecutorService executor = new TestExecutorService();
         CellularIdentifierDisclosureNotifier notifier =
-                new CellularIdentifierDisclosureNotifier(executor, 15, TimeUnit.MINUTES);
+                new CellularIdentifierDisclosureNotifier(
+                        executor, 15, TimeUnit.MINUTES, mSafetySource);
 
-        notifier.enable();
+        notifier.enable(mContext);
 
         for (int i = 0; i < 3; i++) {
-            notifier.addDisclosure(SUB_ID_1, mDislosure);
+            notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
         }
 
         assertEquals(3, notifier.getCurrentDisclosureCount(SUB_ID_1));
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(1), any(), any());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(2), any(), any());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(3), any(), any());
     }
 
     @Test
     public void testSingleDisclosureStartAndEndTimesAreEqual() {
         TestExecutorService executor = new TestExecutorService();
         CellularIdentifierDisclosureNotifier notifier =
-                new CellularIdentifierDisclosureNotifier(executor, 15, TimeUnit.MINUTES);
+                new CellularIdentifierDisclosureNotifier(
+                        executor, 15, TimeUnit.MINUTES, mSafetySource);
 
-        notifier.enable();
+        notifier.enable(mContext);
 
-        notifier.addDisclosure(SUB_ID_1, mDislosure);
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
 
         assertEquals(1, notifier.getCurrentDisclosureCount(SUB_ID_1));
         assertTrue(notifier.getFirstOpen(SUB_ID_1).equals(notifier.getCurrentEnd(SUB_ID_1)));
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(1), any(), any());
     }
 
     @Test
     public void testMultipleDisclosuresTimeWindows() {
         TestExecutorService executor = new TestExecutorService();
         CellularIdentifierDisclosureNotifier notifier =
-                new CellularIdentifierDisclosureNotifier(executor, 15, TimeUnit.MINUTES);
+                new CellularIdentifierDisclosureNotifier(
+                        executor, 15, TimeUnit.MINUTES, mSafetySource);
 
-        notifier.enable();
+        notifier.enable(mContext);
 
-        notifier.addDisclosure(SUB_ID_1, mDislosure);
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
         try {
             Thread.sleep(50);
         } catch (InterruptedException e) {
             throw new RuntimeException(e);
         }
-        notifier.addDisclosure(SUB_ID_1, mDislosure);
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
 
         assertEquals(2, notifier.getCurrentDisclosureCount(SUB_ID_1));
         assertTrue(notifier.getFirstOpen(SUB_ID_1).isBefore(notifier.getCurrentEnd(SUB_ID_1)));
+        verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(1), any(), any());
     }
 
     @Test
     public void testAddDisclosureThenWindowClose() {
         TestExecutorService executor = new TestExecutorService();
         CellularIdentifierDisclosureNotifier notifier =
-                new CellularIdentifierDisclosureNotifier(executor, 15, TimeUnit.MINUTES);
+                new CellularIdentifierDisclosureNotifier(
+                        executor, 15, TimeUnit.MINUTES, mSafetySource);
 
         // One round of disclosures
-        notifier.enable();
-        notifier.addDisclosure(SUB_ID_1, mDislosure);
-        notifier.addDisclosure(SUB_ID_1, mDislosure);
+        notifier.enable(mContext);
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
         assertEquals(2, notifier.getCurrentDisclosureCount(SUB_ID_1));
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(1), any(), any());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(2), any(), any());
 
         // Window close should reset the counter
         executor.advanceTime(WINDOW_CLOSE_ADVANCE_MILLIS);
         assertEquals(0, notifier.getCurrentDisclosureCount(SUB_ID_1));
 
         // A new disclosure should increment as normal
-        notifier.addDisclosure(SUB_ID_1, mDislosure);
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
         assertEquals(1, notifier.getCurrentDisclosureCount(SUB_ID_1));
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(1), any(), any());
     }
 
     @Test
     public void testDisableClosesWindow() {
         TestExecutorService executor = new TestExecutorService();
         CellularIdentifierDisclosureNotifier notifier =
-                new CellularIdentifierDisclosureNotifier(executor, 15, TimeUnit.MINUTES);
+                new CellularIdentifierDisclosureNotifier(
+                        executor, 15, TimeUnit.MINUTES, mSafetySource);
 
         // One round of disclosures
-        notifier.enable();
-        notifier.addDisclosure(SUB_ID_1, mDislosure);
-        notifier.addDisclosure(SUB_ID_1, mDislosure);
+        notifier.enable(mContext);
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
         assertEquals(2, notifier.getCurrentDisclosureCount(SUB_ID_1));
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(1), any(), any());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(2), any(), any());
 
-        notifier.disable();
+        notifier.disable(mContext);
         assertFalse(notifier.isEnabled());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosureIssueEnabled(any(), eq(false));
 
         // We're disabled now so no disclosures should open the disclosure window
-        notifier.addDisclosure(SUB_ID_1, mDislosure);
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
         assertEquals(0, notifier.getCurrentDisclosureCount(SUB_ID_1));
+        mInOrder.verifyNoMoreInteractions();
     }
 
     @Test
     public void testMultipleSubIdsTrackedIndependently() {
         TestExecutorService executor = new TestExecutorService();
         CellularIdentifierDisclosureNotifier notifier =
-                new CellularIdentifierDisclosureNotifier(executor, 15, TimeUnit.MINUTES);
+                new CellularIdentifierDisclosureNotifier(
+                        executor, 15, TimeUnit.MINUTES, mSafetySource);
 
-        notifier.enable();
+        notifier.enable(mContext);
         for (int i = 0; i < 3; i++) {
-            notifier.addDisclosure(SUB_ID_1, mDislosure);
+            notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
         }
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(1), any(), any());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(2), any(), any());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(3), any(), any());
 
         for (int i = 0; i < 4; i++) {
-            notifier.addDisclosure(SUB_ID_2, mDislosure);
+            notifier.addDisclosure(mContext, SUB_ID_2, mDislosure);
         }
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_2), eq(1), any(), any());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_2), eq(2), any(), any());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_2), eq(3), any(), any());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_2), eq(4), any(), any());
 
         assertEquals(3, notifier.getCurrentDisclosureCount(SUB_ID_1));
         assertEquals(4, notifier.getCurrentDisclosureCount(SUB_ID_2));
diff --git a/tests/telephonytests/src/com/android/internal/telephony/security/CellularNetworkSecuritySafetySourceTest.java b/tests/telephonytests/src/com/android/internal/telephony/security/CellularNetworkSecuritySafetySourceTest.java
new file mode 100644
index 0000000..76118c4
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/security/CellularNetworkSecuritySafetySourceTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2024 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.security;
+
+import static com.android.internal.telephony.security.CellularNetworkSecuritySafetySource.NULL_CIPHER_STATE_ENCRYPTED;
+import static com.android.internal.telephony.security.CellularNetworkSecuritySafetySource.NULL_CIPHER_STATE_NOTIFY_ENCRYPTED;
+import static com.android.internal.telephony.security.CellularNetworkSecuritySafetySource.NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.safetycenter.SafetySourceData;
+import android.util.Singleton;
+
+import com.android.internal.R;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.TestApplication;
+import com.android.internal.telephony.security.CellularNetworkSecuritySafetySource.SafetyCenterManagerWrapper;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.time.Instant;
+
+public final class CellularNetworkSecuritySafetySourceTest extends TelephonyTest {
+
+    private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+
+        // unmock ActivityManager to be able to register receiver, create real PendingIntents.
+        restoreInstance(Singleton.class, "mInstance", mIActivityManagerSingleton);
+        restoreInstance(ActivityManager.class, "IActivityManagerSingleton", null);
+
+        SubscriptionInfoInternal info0 = new SubscriptionInfoInternal.Builder()
+                .setId(0)
+                .setDisplayName("fake_name0")
+                .build();
+        doReturn(info0).when(mSubscriptionManagerService).getSubscriptionInfoInternal(eq(0));
+        SubscriptionInfoInternal info1 = new SubscriptionInfoInternal.Builder()
+                .setId(1)
+                .setDisplayName("fake_name1")
+                .build();
+        doReturn(info1).when(mSubscriptionManagerService).getSubscriptionInfoInternal(eq(1));
+
+        mContextFixture.putResource(R.string.scCellularNetworkSecurityTitle, "fake");
+        mContextFixture.putResource(R.string.scCellularNetworkSecuritySummary, "fake");
+        mContextFixture.putResource(R.string.scNullCipherIssueNonEncryptedTitle, "fake %1$s");
+        mContextFixture.putResource(R.string.scNullCipherIssueNonEncryptedSummary, "fake");
+        mContextFixture.putResource(R.string.scNullCipherIssueEncryptedTitle, "fake %1$s");
+        mContextFixture.putResource(R.string.scNullCipherIssueEncryptedSummary, "fake");
+        mContextFixture.putResource(R.string.scIdentifierDisclosureIssueTitle, "fake");
+        mContextFixture.putResource(
+                R.string.scIdentifierDisclosureIssueSummary, "fake %1$d %2$tr %3$tr %4$s");
+        mContextFixture.putResource(R.string.scNullCipherIssueActionSettings, "fake");
+        mContextFixture.putResource(R.string.scNullCipherIssueActionLearnMore, "fake");
+
+        mSafetyCenterManagerWrapper = mock(SafetyCenterManagerWrapper.class);
+        doAnswer(inv -> getActivityPendingIntent(inv.getArgument(1)))
+                .when(mSafetyCenterManagerWrapper)
+                .getActivityPendingIntent(any(Context.class), any(Intent.class));
+
+        mSafetySource = new CellularNetworkSecuritySafetySource(mSafetyCenterManagerWrapper);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private PendingIntent getActivityPendingIntent(Intent intent) {
+        Context context = TestApplication.getAppContext();
+        return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
+    }
+
+    @Test
+    public void disableNullCipherIssue_nullData() {
+        mSafetySource.setNullCipherIssueEnabled(mContext, false);
+
+        verify(mSafetyCenterManagerWrapper, times(1)).setSafetySourceData(isNull());
+    }
+
+    @Test
+    public void enableNullCipherIssue_statusWithoutIssues() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setNullCipherIssueEnabled(mContext, true);
+
+        verify(mSafetyCenterManagerWrapper, times(1)).setSafetySourceData(data.capture());
+        assertThat(data.getValue().getStatus()).isNotNull();
+        assertThat(data.getValue().getIssues()).isEmpty();
+    }
+
+    @Test
+    public void setNullCipherState_encrypted_statusWithoutIssue() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setNullCipherIssueEnabled(mContext, true);
+        mSafetySource.setNullCipherState(mContext, 0, NULL_CIPHER_STATE_ENCRYPTED);
+
+        verify(mSafetyCenterManagerWrapper, times(2)).setSafetySourceData(data.capture());
+        assertThat(data.getAllValues().get(1).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(1).getIssues()).isEmpty();
+    }
+
+    @Test
+    public void setNullCipherState_notifyEncrypted_statusWithIssue() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setNullCipherIssueEnabled(mContext, true);
+        mSafetySource.setNullCipherState(mContext, 0, NULL_CIPHER_STATE_NOTIFY_ENCRYPTED);
+
+        verify(mSafetyCenterManagerWrapper, times(2)).setSafetySourceData(data.capture());
+        assertThat(data.getAllValues().get(1).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(1).getIssues()).hasSize(1);
+    }
+
+    @Test
+    public void setNullCipherState_notifyNonEncrypted_statusWithIssue() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setNullCipherIssueEnabled(mContext, true);
+        mSafetySource.setNullCipherState(mContext, 0, NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED);
+
+        verify(mSafetyCenterManagerWrapper, times(2)).setSafetySourceData(data.capture());
+        assertThat(data.getAllValues().get(1).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(1).getIssues()).hasSize(1);
+    }
+
+    @Test
+    public void setNullCipherState_multipleNonEncrypted_statusWithTwoIssues() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setNullCipherIssueEnabled(mContext, true);
+        mSafetySource.setNullCipherState(mContext, 0, NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED);
+        mSafetySource.setNullCipherState(mContext, 1, NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED);
+
+        verify(mSafetyCenterManagerWrapper, times(3)).setSafetySourceData(data.capture());
+        assertThat(data.getAllValues().get(2).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(2).getIssues()).hasSize(2);
+    }
+
+    @Test
+    public void disableIdentifierDisclosueIssue_nullData() {
+        // We must first enable before disabling, since a standalone call to disable may result in
+        // a no-op when the default for a new notifier is to be disabled.
+        mSafetySource.setIdentifierDisclosureIssueEnabled(mContext, true);
+        mSafetySource.setIdentifierDisclosureIssueEnabled(mContext, false);
+
+        verify(mSafetyCenterManagerWrapper, times(1)).setSafetySourceData(isNull());
+    }
+
+    @Test
+    public void enableIdentifierDisclosureIssue_enableTwice() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+        mSafetySource.setIdentifierDisclosureIssueEnabled(mContext, true);
+        mSafetySource.setIdentifierDisclosure(mContext, 0, 12, Instant.now(), Instant.now());
+        mSafetySource.setIdentifierDisclosureIssueEnabled(mContext, true);
+
+        // Two invocations because the initial enablement and the subsequent disclosure result in
+        // updates to safety center
+        verify(mSafetyCenterManagerWrapper, times(2)).setSafetySourceData(data.capture());
+        // When we're enabled, enabling again should not clear our issue list.
+        assertThat(data.getAllValues().get(1).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(1).getIssues()).hasSize(1);
+    }
+
+    @Test
+    public void enableIdentifierDisclosueIssue_statusWithoutIssues() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setIdentifierDisclosureIssueEnabled(mContext, true);
+
+        verify(mSafetyCenterManagerWrapper, times(1)).setSafetySourceData(data.capture());
+        assertThat(data.getValue().getStatus()).isNotNull();
+        assertThat(data.getValue().getIssues()).isEmpty();
+    }
+
+    @Test
+    public void setIdentifierDisclosure_singleDisclosure_statusWithIssue() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setIdentifierDisclosureIssueEnabled(mContext, true);
+        mSafetySource.setIdentifierDisclosure(mContext, 0, 12, Instant.now(), Instant.now());
+
+        verify(mSafetyCenterManagerWrapper, times(2)).setSafetySourceData(data.capture());
+        assertThat(data.getAllValues().get(1).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(1).getIssues()).hasSize(1);
+    }
+
+    @Test
+    public void setIdentifierDisclosure_multipleDisclosures_statusWithTwoIssues() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setIdentifierDisclosureIssueEnabled(mContext, true);
+        mSafetySource.setIdentifierDisclosure(mContext, 0, 12, Instant.now(), Instant.now());
+        mSafetySource.setIdentifierDisclosure(mContext, 1, 3, Instant.now(), Instant.now());
+
+        verify(mSafetyCenterManagerWrapper, times(3)).setSafetySourceData(data.capture());
+        assertThat(data.getAllValues().get(2).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(2).getIssues()).hasSize(2);
+    }
+
+    @Test
+    public void multipleIssuesKinds_statusWithTwoIssues() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setNullCipherIssueEnabled(mContext, true);
+        mSafetySource.setNullCipherState(mContext, 0, NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED);
+        mSafetySource.setIdentifierDisclosureIssueEnabled(mContext, true);
+        mSafetySource.setIdentifierDisclosure(mContext, 0, 12, Instant.now(), Instant.now());
+
+        verify(mSafetyCenterManagerWrapper, times(4)).setSafetySourceData(data.capture());
+        assertThat(data.getAllValues().get(3).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(3).getIssues()).hasSize(2);
+    }
+}
\ No newline at end of file
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 1a89c15..7339e42 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
@@ -128,12 +128,19 @@
             SubscriptionManager.SERVICE_CAPABILITY_DATA_BITMASK;
     static final int FAKE_SERVICE_CAPABILITIES_2 =
             SubscriptionManager.SERVICE_CAPABILITY_SMS_BITMASK;
+    static final int FAKE_SATELLITE_ENTITLEMENT_STATUS_ENABLED = 1;
+    static final int FAKE_SATELLITE_ENTITLEMENT_STATUS_DISABLED = 0;
+    static final String FAKE_SATELLITE_ENTITLEMENT_PLMNS1 = "123123,12310";
+    static final String FAKE_SATELLITE_ENTITLEMENT_PLMNS2 = "";
 
     static final String FAKE_MAC_ADDRESS1 = "DC:E5:5B:38:7D:40";
     static final String FAKE_MAC_ADDRESS2 = "DC:B5:4F:47:F3:4C";
 
     private FeatureFlags mFeatureFlags;
 
+    static final int FAKE_TRANSFER_STATUS_TRANSFERRED_OUT = 1;
+    static final int FAKE_TRANSFER_STATUS_CONVERTED = 2;
+
     static final SubscriptionInfoInternal FAKE_SUBSCRIPTION_INFO1 =
             new SubscriptionInfoInternal.Builder()
                     .setId(1)
@@ -204,6 +211,9 @@
                     .setOnlyNonTerrestrialNetwork(FAKE_SATELLITE_IS_NTN_DISABLED)
                     .setGroupDisabled(false)
                     .setServiceCapabilities(FAKE_SERVICE_CAPABILITIES_1)
+                    .setTransferStatus(FAKE_TRANSFER_STATUS_TRANSFERRED_OUT)
+                    .setSatelliteEntitlementStatus(FAKE_SATELLITE_ENTITLEMENT_STATUS_DISABLED)
+                    .setSatelliteEntitlementPlmns(FAKE_SATELLITE_ENTITLEMENT_PLMNS2)
                     .build();
 
     static final SubscriptionInfoInternal FAKE_SUBSCRIPTION_INFO2 =
@@ -276,6 +286,9 @@
                     .setOnlyNonTerrestrialNetwork(FAKE_SATELLITE_IS_NTN_ENABLED)
                     .setGroupDisabled(false)
                     .setServiceCapabilities(FAKE_SERVICE_CAPABILITIES_2)
+                    .setTransferStatus(FAKE_TRANSFER_STATUS_CONVERTED)
+                    .setSatelliteEntitlementStatus(FAKE_SATELLITE_ENTITLEMENT_STATUS_ENABLED)
+                    .setSatelliteEntitlementPlmns(FAKE_SATELLITE_ENTITLEMENT_PLMNS1)
                     .build();
 
     private SubscriptionDatabaseManager mDatabaseManagerUT;
@@ -436,6 +449,7 @@
         doReturn(2).when(mUiccController).convertToPublicCardId(eq(FAKE_ICCID2));
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
         when(mFeatureFlags.dataOnlyCellularService()).thenReturn(true);
+        when(mFeatureFlags.supportPsimToEsimConversion()).thenReturn(true);
         mDatabaseManagerUT = new SubscriptionDatabaseManager(mContext, Looper.myLooper(),
                 mFeatureFlags, mSubscriptionDatabaseManagerCallback);
         logd("SubscriptionDatabaseManagerTest -Setup!");
@@ -706,7 +720,10 @@
         assertThrows(IllegalArgumentException.class,
                 () -> mDatabaseManagerUT.setNumber(1, FAKE_PHONE_NUMBER2));
 
-        SubscriptionInfoInternal subInfo = insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1);
+        // Prevent the carrier number from overriding the display number
+        SubscriptionInfoInternal subInfo = insertSubscriptionAndVerify(
+                new SubscriptionInfoInternal.Builder(FAKE_SUBSCRIPTION_INFO1)
+                        .setNumberFromCarrier("").build());
         mDatabaseManagerUT.setNumber(subInfo.getSubscriptionId(), FAKE_PHONE_NUMBER2);
         processAllMessages();
 
@@ -2239,4 +2256,105 @@
                 mDatabaseManagerUT.getSubscriptionInfoInternal(1).getServiceCapabilities())
                 .isEqualTo(FAKE_SERVICE_CAPABILITIES_1);
     }
+
+    @Test
+    public void testSetTransferStatus() throws Exception {
+        // exception is expected if there is nothing in the database.
+        assertThrows(IllegalArgumentException.class,
+                () -> mDatabaseManagerUT.setTransferStatus(
+                        FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                        FAKE_TRANSFER_STATUS_TRANSFERRED_OUT));
+
+        SubscriptionInfoInternal subInfo = insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1);
+        mDatabaseManagerUT.setTransferStatus(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                FAKE_TRANSFER_STATUS_TRANSFERRED_OUT);
+        processAllMessages();
+
+        subInfo = new SubscriptionInfoInternal.Builder(subInfo)
+                .setTransferStatus(FAKE_TRANSFER_STATUS_TRANSFERRED_OUT)
+                .build();
+        verifySubscription(subInfo);
+        verify(mSubscriptionDatabaseManagerCallback, times(1)).onSubscriptionChanged(eq(1));
+
+        assertThat(mDatabaseManagerUT.getSubscriptionProperty(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_TRANSFER_STATUS)).isEqualTo(FAKE_TRANSFER_STATUS_TRANSFERRED_OUT);
+
+        mDatabaseManagerUT.setSubscriptionProperty(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_TRANSFER_STATUS, FAKE_TRANSFER_STATUS_CONVERTED);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId()).getTransferStatus())
+                .isEqualTo(FAKE_TRANSFER_STATUS_CONVERTED);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId()).getTransferStatus())
+                .isNotEqualTo(FAKE_TRANSFER_STATUS_TRANSFERRED_OUT);
+    }
+
+    @Test
+    public void testUpdateSatelliteEntitlementStatus() throws Exception {
+        // exception is expected if there is nothing in the database.
+        assertThrows(IllegalArgumentException.class,
+                () -> mDatabaseManagerUT.setSatelliteEntitlementStatus(
+                        FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                        FAKE_SATELLITE_ENTITLEMENT_STATUS_ENABLED));
+
+        SubscriptionInfoInternal subInfo = insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1);
+        mDatabaseManagerUT.setSatelliteEntitlementStatus(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                FAKE_SATELLITE_ENTITLEMENT_STATUS_ENABLED);
+        processAllMessages();
+
+        subInfo = new SubscriptionInfoInternal.Builder(subInfo)
+                .setSatelliteEntitlementStatus(FAKE_SATELLITE_ENTITLEMENT_STATUS_ENABLED)
+                .build();
+        verifySubscription(subInfo);
+        verify(mSubscriptionDatabaseManagerCallback, times(2)).onSubscriptionChanged(eq(1));
+
+        assertThat(mDatabaseManagerUT.getSubscriptionProperty(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS))
+                .isEqualTo(FAKE_SATELLITE_ENTITLEMENT_STATUS_ENABLED);
+
+        mDatabaseManagerUT.setSubscriptionProperty(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
+                FAKE_SATELLITE_ENTITLEMENT_STATUS_DISABLED);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
+                        FAKE_SUBSCRIPTION_INFO1.getSubscriptionId())
+                .getSatelliteEntitlementStatus())
+                .isEqualTo(FAKE_SATELLITE_ENTITLEMENT_STATUS_DISABLED);
+    }
+
+    @Test
+    public void testUpdateSatelliteEntitlementPlmns() throws Exception {
+        // exception is expected if there is nothing in the database.
+        assertThrows(IllegalArgumentException.class,
+                () -> mDatabaseManagerUT.setSatelliteEntitlementPlmns(
+                        FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                        FAKE_SATELLITE_ENTITLEMENT_PLMNS1));
+
+        SubscriptionInfoInternal subInfo = insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1);
+        mDatabaseManagerUT.setSatelliteEntitlementPlmns(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                FAKE_SATELLITE_ENTITLEMENT_PLMNS1);
+        processAllMessages();
+
+        subInfo = new SubscriptionInfoInternal.Builder(subInfo)
+                .setSatelliteEntitlementPlmns(FAKE_SATELLITE_ENTITLEMENT_PLMNS1)
+                .build();
+        verifySubscription(subInfo);
+        verify(mSubscriptionDatabaseManagerCallback, times(2)).onSubscriptionChanged(eq(1));
+
+        assertThat(mDatabaseManagerUT.getSubscriptionProperty(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS))
+                .isEqualTo(FAKE_SATELLITE_ENTITLEMENT_PLMNS1);
+
+        mDatabaseManagerUT.setSubscriptionProperty(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS,
+                FAKE_SATELLITE_ENTITLEMENT_PLMNS2);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
+                        FAKE_SUBSCRIPTION_INFO1.getSubscriptionId())
+                .getSatelliteEntitlementPlmns())
+                .isEqualTo(FAKE_SATELLITE_ENTITLEMENT_PLMNS2);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java
index d9addd1..6e2c5bf 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java
@@ -115,6 +115,13 @@
                     .setOnlyNonTerrestrialNetwork(1)
                     .setServiceCapabilities(
                             SubscriptionManager.SERVICE_CAPABILITY_DATA_BITMASK)
+                    .setTransferStatus(1)
+                    .setSatelliteEntitlementStatus(
+                            SubscriptionDatabaseManagerTest
+                                    .FAKE_SATELLITE_ENTITLEMENT_STATUS_ENABLED)
+                    .setSatelliteEntitlementPlmns(
+                            SubscriptionDatabaseManagerTest
+                                    .FAKE_SATELLITE_ENTITLEMENT_PLMNS1)
                     .build();
 
     private final SubscriptionInfoInternal mSubInfoNull =
@@ -141,6 +148,7 @@
                     .setRcsConfig(new byte[0])
                     .setAllowedNetworkTypesForReasons("")
                     .setDeviceToDeviceStatusSharingContacts("")
+                    .setTransferStatus(1)
                     .build();
 
     @Rule
@@ -237,6 +245,13 @@
         assertThat(mSubInfo.getOnlyNonTerrestrialNetwork()).isEqualTo(1);
         assertThat(mSubInfo.getServiceCapabilities()).isEqualTo(
                 SubscriptionManager.SERVICE_CAPABILITY_DATA_BITMASK);
+        assertThat(mSubInfo.getTransferStatus()).isEqualTo(1);
+        assertThat(mSubInfo.getSatelliteEntitlementStatus())
+                .isEqualTo(SubscriptionDatabaseManagerTest
+                        .FAKE_SATELLITE_ENTITLEMENT_STATUS_ENABLED);
+        assertThat(mSubInfo.getSatelliteEntitlementPlmns())
+                .isEqualTo(SubscriptionDatabaseManagerTest
+                        .FAKE_SATELLITE_ENTITLEMENT_PLMNS1);
     }
 
     @Test
@@ -303,6 +318,7 @@
         assertThat(subInfo.isOnlyNonTerrestrialNetwork()).isTrue();
         assertThat(subInfo.getServiceCapabilities()).isEqualTo(
                 Set.of(SubscriptionManager.SERVICE_CAPABILITY_DATA));
+        assertThat(mSubInfo.getTransferStatus()).isEqualTo(1);
     }
 
     @Test
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 0aeaad3..defa730 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
@@ -353,6 +353,11 @@
         assertThat(subInfo.getSimSlotIndex()).isEqualTo(0);
         assertThat(subInfo.getSubscriptionType()).isEqualTo(
                 SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
+
+        // Invalid slot index should trigger IllegalArgumentException
+        assertThrows(IllegalArgumentException.class,
+                () -> mSubscriptionManagerServiceUT.addSubInfo(FAKE_ICCID1, FAKE_CARRIER_NAME1,
+                        2, SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM));
     }
 
     @Test
@@ -414,6 +419,23 @@
     }
 
     @Test
+    public void testSetAdminOwned() {
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
+        mSubscriptionManagerServiceUT.addSubInfo(FAKE_ICCID1, FAKE_CARRIER_NAME1,
+                0, SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
+        processAllMessages();
+        String groupOwner = "test";
+
+        mSubscriptionManagerServiceUT.setGroupOwner(1, groupOwner);
+
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerServiceUT
+                .getSubscriptionInfoInternal(1);
+        assertThat(subInfo).isNotNull();
+        assertThat(subInfo.getGroupOwner()).isEqualTo(groupOwner);
+        verify(mMockedSubscriptionManagerServiceCallback).onSubscriptionChanged(eq(1));
+    }
+
+    @Test
     @DisableCompatChanges({TelephonyManager.ENABLE_FEATURE_MAPPING})
     public void testSetPhoneNumber() {
         doReturn(false).when(mFlags).enforceTelephonyFeatureMapping();
@@ -1589,7 +1611,9 @@
 
     @Test
     public void testSetDisplayNumber() {
-        insertSubscription(FAKE_SUBSCRIPTION_INFO1);
+        insertSubscription(new SubscriptionInfoInternal.Builder(FAKE_SUBSCRIPTION_INFO1)
+                .setNumberFromCarrier("")
+                .build());
 
         // Should fail without MODIFY_PHONE_STATE
         assertThrows(SecurityException.class, () -> mSubscriptionManagerServiceUT
@@ -2146,6 +2170,39 @@
     }
 
     @Test
+    public void testGetNumberWithCarrierNumber() {
+        insertSubscription(FAKE_SUBSCRIPTION_INFO1);
+
+        // Should fail without MODIFY_PHONE_STATE
+        assertThrows(SecurityException.class, () -> mSubscriptionManagerServiceUT
+                .setDisplayNumber(FAKE_PHONE_NUMBER2, 1));
+
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
+
+        mSubscriptionManagerServiceUT.setDisplayNumber(FAKE_PHONE_NUMBER2, 1);
+        processAllMessages();
+        verify(mMockedSubscriptionManagerServiceCallback).onSubscriptionChanged(eq(1));
+
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerServiceUT
+                .getSubscriptionInfoInternal(1);
+        assertThat(subInfo).isNotNull();
+        assertThat(subInfo.getNumber()).isEqualTo(FAKE_PHONE_NUMBER1);
+        Mockito.clearInvocations(mMockedSubscriptionManagerServiceCallback);
+
+        setCarrierPrivilegesForSubId(true, 1);
+        mSubscriptionManagerServiceUT.setPhoneNumber(1,
+                SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER, "",
+                CALLING_PACKAGE, CALLING_FEATURE);
+        processAllMessages();
+        verify(mMockedSubscriptionManagerServiceCallback).onSubscriptionChanged(eq(1));
+        setCarrierPrivilegesForSubId(false, 1);
+
+        subInfo = mSubscriptionManagerServiceUT.getSubscriptionInfoInternal(1);
+        assertThat(subInfo).isNotNull();
+        assertThat(subInfo.getNumber()).isEqualTo(FAKE_PHONE_NUMBER2);
+    }
+
+    @Test
     public void testGetNonAccessibleFields() throws Exception {
         insertSubscription(FAKE_SUBSCRIPTION_INFO1);