Merge "Dump state of CarrierServiceBindHelper in UiccController.dump" into main
diff --git a/flags/data.aconfig b/flags/data.aconfig
index 3beb016..6334803 100644
--- a/flags/data.aconfig
+++ b/flags/data.aconfig
@@ -1,10 +1,13 @@
 package: "com.android.internal.telephony.flags"
 
 flag {
-  name: "auto_switch_allow_roaming"
+  name: "auto_data_switch_allow_roaming"
   namespace: "telephony"
   description: "Allow using roaming network as target if user allows it from settings."
-  bug: "306488039"
+  bug: "287132491"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
 
 flag {
@@ -117,4 +120,11 @@
   namespace: "telephony"
   description: "This flag is for internal implementation to handle reconnect request from QNS in telephony FWK."
   bug: "319520561"
+}
+
+flag {
+  name: "dsrs_diagnostics_enabled"
+  namespace: "telephony"
+  description: "Enable DSRS diagnostics."
+  bug: "319601607"
 }
\ No newline at end of file
diff --git a/flags/ims.aconfig b/flags/ims.aconfig
index d09259e..b2cc904 100644
--- a/flags/ims.aconfig
+++ b/flags/ims.aconfig
@@ -62,3 +62,10 @@
     description: "This flag updates roaming state to set wfc mode"
     bug:"317298331"
 }
+
+flag {
+    name: "enable_sip_subscribe_retry"
+    namespace: "telephony"
+    description: "This flag controls whether framework supports SIP subscribe retry or not"
+    bug:"297023230"
+}
diff --git a/flags/telephony.aconfig b/flags/telephony.aconfig
index d59b249..9ef70b1 100644
--- a/flags/telephony.aconfig
+++ b/flags/telephony.aconfig
@@ -26,4 +26,18 @@
     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
+}
+
+flag {
+    name: "minimal_telephony_cdm_check"
+    namespace: "telephony"
+    description: "This flag disables Calling/Data/Messaging features if their respective feature is missing"
+    bug: "310710841"
+}
+
+flag {
+    name: "minimal_telephony_managers_conditional_on_features"
+    namespace: "telephony"
+    description: "This flag enables checking for telephony features before initializing corresponding managers"
+    bug: "310710841"
+}
diff --git a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
index 5517bc6..9113514 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.os.AsyncResult;
 import android.os.Build;
 import android.os.Bundle;
@@ -159,6 +160,12 @@
     public GsmCdmaCallTracker(GsmCdmaPhone phone, FeatureFlags featureFlags) {
         super(featureFlags);
 
+        if (mFeatureFlags.minimalTelephonyCdmCheck()
+                && !phone.getContext().getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_TELEPHONY_CALLING)) {
+            throw new UnsupportedOperationException("GsmCdmaCallTracker requires calling");
+        }
+
         this.mPhone = phone;
         mCi = phone.mCi;
         mCi.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null);
@@ -1492,7 +1499,7 @@
 
         switch (msg.what) {
             case EVENT_POLL_CALLS_RESULT:
-                Rlog.d(LOG_TAG, "Event EVENT_POLL_CALLS_RESULT Received");
+                if (DBG_POLL) Rlog.d(LOG_TAG, "Event EVENT_POLL_CALLS_RESULT Received");
 
                 if (msg == mLastRelevantPoll) {
                     if (DBG_POLL) log(
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index aca759b..de7ebd6 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -42,6 +42,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
 import android.database.SQLException;
 import android.hardware.radio.modem.ImeiInfo;
 import android.net.Uri;
@@ -370,9 +371,11 @@
                 SignalStrengthController.class.getName()).makeSignalStrengthController(this);
         mSST = mTelephonyComponentFactory.inject(ServiceStateTracker.class.getName())
                 .makeServiceStateTracker(this, this.mCi, featureFlags);
-        mEmergencyNumberTracker = mTelephonyComponentFactory
-                .inject(EmergencyNumberTracker.class.getName()).makeEmergencyNumberTracker(
-                        this, this.mCi);
+        if (hasCalling()) {
+            mEmergencyNumberTracker = mTelephonyComponentFactory
+                    .inject(EmergencyNumberTracker.class.getName()).makeEmergencyNumberTracker(
+                            this, this.mCi, mFeatureFlags);
+        }
         mDeviceStateMonitor = mTelephonyComponentFactory.inject(DeviceStateMonitor.class.getName())
                 .makeDeviceStateMonitor(this, mFeatureFlags);
 
@@ -412,9 +415,11 @@
 
         mCallWaitingController = new CallWaitingController(this);
 
-        loadTtyMode();
+        if (hasCalling()) {
+            loadTtyMode();
 
-        CallManager.getInstance().registerPhone(this);
+            CallManager.getInstance().registerPhone(this);
+        }
 
         mSubscriptionsChangedListener =
                 new SubscriptionManager.OnSubscriptionsChangedListener() {
@@ -464,13 +469,21 @@
         }
     };
 
+    private boolean hasCalling() {
+        if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true;
+        return mContext.getPackageManager().hasSystemFeature(
+            PackageManager.FEATURE_TELEPHONY_CALLING);
+    }
+
     private void initOnce(CommandsInterface ci) {
         if (ci instanceof SimulatedRadioControl) {
             mSimulatedRadioControl = (SimulatedRadioControl) ci;
         }
 
-        mCT = mTelephonyComponentFactory.inject(GsmCdmaCallTracker.class.getName())
-                .makeGsmCdmaCallTracker(this, mFeatureFlags);
+        if (hasCalling()) {
+            mCT = mTelephonyComponentFactory.inject(GsmCdmaCallTracker.class.getName())
+                    .makeGsmCdmaCallTracker(this, mFeatureFlags);
+        }
         mIccPhoneBookIntManager = mTelephonyComponentFactory
                 .inject(IccPhoneBookInterfaceManager.class.getName())
                 .makeIccPhoneBookInterfaceManager(this);
@@ -558,7 +571,7 @@
             mNullCipherNotifier =
                     mTelephonyComponentFactory
                             .inject(NullCipherNotifier.class.getName())
-                            .makeNullCipherNotifier();
+                            .makeNullCipherNotifier(mSafetySource);
             mCi.registerForSecurityAlgorithmUpdates(
                     this, EVENT_SECURITY_ALGORITHM_UPDATE, null);
         }
@@ -693,7 +706,7 @@
         unregisterForIccRecordEvents();
         registerForIccRecordEvents();
 
-        mCT.updatePhoneType();
+        if (mCT != null) mCT.updatePhoneType();
 
         int radioState = mCi.getRadioState();
         if (radioState != TelephonyManager.RADIO_POWER_UNAVAILABLE) {
@@ -753,6 +766,8 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @Override
     public PhoneConstants.State getState() {
+        if (!hasCalling()) return PhoneConstants.State.IDLE;
+
         if (mImsPhone != null) {
             PhoneConstants.State imsState = mImsPhone.getState();
             if (imsState != PhoneConstants.State.IDLE) {
@@ -837,6 +852,7 @@
 
     @Override
     public boolean isDataSuspended() {
+        if (mCT == null) return false;
         return mCT.mState != PhoneConstants.State.IDLE && !mSST.isConcurrentVoiceAndDataAllowed();
     }
 
@@ -884,7 +900,7 @@
 
     @Override
     public boolean isInEmergencyCall() {
-        if (isPhoneTypeGsm()) {
+        if (!hasCalling() || isPhoneTypeGsm()) {
             return false;
         } else {
             return mCT.isInEmergencyCall();
@@ -893,7 +909,7 @@
 
     @Override
     protected void setIsInEmergencyCall() {
-        if (!isPhoneTypeGsm()) {
+        if (!hasCalling() && !isPhoneTypeGsm()) {
             mCT.setIsInEmergencyCall();
         }
     }
@@ -985,6 +1001,7 @@
 
     @Override
     public void acceptCall(int videoState) throws CallStateException {
+        if (!hasCalling()) throw new CallStateException();
         Phone imsPhone = mImsPhone;
         if ( imsPhone != null && imsPhone.getRingingCall().isRinging() ) {
             imsPhone.acceptCall(videoState);
@@ -995,6 +1012,7 @@
 
     @Override
     public void rejectCall() throws CallStateException {
+        if (!hasCalling()) throw new CallStateException();
         mCT.rejectCall();
     }
 
@@ -1025,6 +1043,7 @@
 
     @Override
     public boolean canConference() {
+        if (!hasCalling()) return false;
         if (mImsPhone != null && mImsPhone.canConference()) {
             return true;
         }
@@ -1075,12 +1094,13 @@
 
     @Override
     public void clearDisconnected() {
+        if (!hasCalling()) return;
         mCT.clearDisconnected();
     }
 
     @Override
     public boolean canTransfer() {
-        if (isPhoneTypeGsm()) {
+        if (hasCalling() && isPhoneTypeGsm()) {
             return mCT.canTransfer();
         } else {
             loge("canTransfer: not possible in CDMA");
@@ -1090,7 +1110,7 @@
 
     @Override
     public void explicitCallTransfer() {
-        if (isPhoneTypeGsm()) {
+        if (hasCalling() && isPhoneTypeGsm()) {
             mCT.explicitCallTransfer();
         } else {
             loge("explicitCallTransfer: not possible in CDMA");
@@ -1104,11 +1124,13 @@
 
     @Override
     public GsmCdmaCall getBackgroundCall() {
+        if (!hasCalling()) return null;
         return mCT.mBackgroundCall;
     }
 
     @Override
     public Call getRingingCall() {
+        if (!hasCalling()) return null;
         Phone imsPhone = mImsPhone;
         // It returns the ringing call of ImsPhone if the ringing call of GSMPhone isn't ringing.
         // In CallManager.registerPhone(), it always registers ringing call of ImsPhone, because
@@ -1184,7 +1206,7 @@
 
     private boolean handleCallDeflectionIncallSupplementaryService(
             String dialString) {
-        if (dialString.length() > 1) {
+        if (!hasCalling() || dialString.length() > 1) {
             return false;
         }
 
@@ -1209,7 +1231,7 @@
     private boolean handleCallWaitingIncallSupplementaryService(String dialString) {
         int len = dialString.length();
 
-        if (len > 2) {
+        if (!hasCalling() || len > 2) {
             return false;
         }
 
@@ -1429,6 +1451,9 @@
     @Override
     public Connection dial(String dialString, @NonNull DialArgs dialArgs,
             Consumer<Phone> chosenPhoneConsumer) throws CallStateException {
+        if (!hasCalling()) {
+            throw new CallStateException("Calling feature is not supported!");
+        }
         if (!isPhoneTypeGsm() && dialArgs.uusInfo != null) {
             throw new CallStateException("Sending UUS information NOT supported in CDMA!");
         }
@@ -2148,7 +2173,9 @@
 
     @Override
     public int getEmergencyNumberDbVersion() {
-        return getEmergencyNumberTracker().getEmergencyNumberDbVersion();
+        EmergencyNumberTracker tracker = getEmergencyNumberTracker();
+        if (tracker == null) return -1;
+        return tracker.getEmergencyNumberDbVersion();
     }
 
     @Override
@@ -3134,6 +3161,8 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void syncClirSetting() {
+        if (!hasCalling()) return;
+
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
         migrateClirSettingIfNeeded(sp);
 
@@ -3339,8 +3368,10 @@
                 if (b != null) {
                     updateBroadcastEmergencyCallStateChangesAfterCarrierConfigChanged(b);
                     updateCdmaRoamingSettingsAfterCarrierConfigChanged(b);
-                    updateNrSettingsAfterCarrierConfigChanged(b);
-                    updateVoNrSettings(b);
+                    if (hasCalling()) {
+                        updateNrSettingsAfterCarrierConfigChanged(b);
+                        updateVoNrSettings(b);
+                    }
                     updateSsOverCdmaSupported(b);
                     updateCarrierN1ModeSupported(b);
                 } else {
@@ -3738,7 +3769,7 @@
                         && mNullCipherNotifier != null) {
                     ar = (AsyncResult) msg.obj;
                     SecurityAlgorithmUpdate update = (SecurityAlgorithmUpdate) ar.result;
-                    mNullCipherNotifier.onSecurityAlgorithmUpdate(getPhoneId(), update);
+                    mNullCipherNotifier.onSecurityAlgorithmUpdate(mContext, getSubId(), update);
                 }
                 break;
 
@@ -4912,6 +4943,7 @@
      * Handler of RIL Voice Radio Technology changed event.
      */
     private void onVoiceRegStateOrRatChanged(int vrs, int vrat) {
+        if (!hasCalling()) return;
         logd("onVoiceRegStateOrRatChanged");
         mCT.dispatchCsCallRadioTech(getCsCallRadioTech(vrs, vrat));
     }
@@ -5113,6 +5145,8 @@
      * Load the current TTY mode in GsmCdmaPhone based on Telecom and UI settings.
      */
     private void loadTtyMode() {
+        if (!hasCalling()) return;
+
         int ttyMode = TelecomManager.TTY_MODE_OFF;
         TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
         if (telecomManager != null) {
@@ -5392,9 +5426,9 @@
         // enable/disable API.
         if (mFeatureFlags.enableModemCipherTransparencyUnsolEvents()) {
             if (prefEnabled) {
-                mNullCipherNotifier.enable();
+                mNullCipherNotifier.enable(mContext);
             } else {
-                mNullCipherNotifier.disable();
+                mNullCipherNotifier.disable(mContext);
             }
         } else {
             logi(
diff --git a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
index 4e4d55d..4146c24 100644
--- a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
+++ b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
@@ -203,12 +203,12 @@
                         mTrackers.remove(token);
                         mPhone.notifySmsSent(tracker.mDestAddress);
                         mSmsDispatchersController.notifySmsSentToEmergencyStateTracker(
-                                tracker.mDestAddress, tracker.mMessageId);
+                                tracker.mDestAddress, tracker.mMessageId, true);
                         break;
                     case ImsSmsImplBase.SEND_STATUS_ERROR:
                         tracker.onFailed(mContext, reason, networkReasonCode);
                         mTrackers.remove(token);
-                        notifySmsSentFailedToEmergencyStateTracker(tracker);
+                        notifySmsSentFailedToEmergencyStateTracker(tracker, true);
                         break;
                     case ImsSmsImplBase.SEND_STATUS_ERROR_RETRY:
                         int maxRetryCountOverIms = getMaxRetryCountOverIms();
@@ -227,7 +227,7 @@
                         } else {
                             tracker.onFailed(mContext, reason, networkReasonCode);
                             mTrackers.remove(token);
-                            notifySmsSentFailedToEmergencyStateTracker(tracker);
+                            notifySmsSentFailedToEmergencyStateTracker(tracker, true);
                         }
                         break;
                     case ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK:
@@ -304,6 +304,11 @@
                     switch (result) {
                         case Intents.RESULT_SMS_HANDLED:
                             mappedResult = ImsSmsImplBase.DELIVER_STATUS_OK;
+                            if (message != null) {
+                                mSmsDispatchersController
+                                        .notifySmsReceivedViaImsToEmergencyStateTracker(
+                                                message.getOriginatingAddress());
+                            }
                             break;
                         case Intents.RESULT_SMS_OUT_OF_MEMORY:
                             mappedResult = ImsSmsImplBase.DELIVER_STATUS_ERROR_NO_MEMORY;
diff --git a/src/java/com/android/internal/telephony/MultiSimSettingController.java b/src/java/com/android/internal/telephony/MultiSimSettingController.java
index 8488ab0..aaeba23 100644
--- a/src/java/com/android/internal/telephony/MultiSimSettingController.java
+++ b/src/java/com/android/internal/telephony/MultiSimSettingController.java
@@ -35,6 +35,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Looper;
@@ -52,6 +53,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.util.ArrayUtils;
@@ -122,6 +124,7 @@
 
     protected final Context mContext;
     private final SubscriptionManagerService mSubscriptionManagerService;
+    private final @NonNull FeatureFlags mFeatureFlags;
 
     // Keep a record of active primary (non-opportunistic) subscription list.
     @NonNull private List<Integer> mPrimarySubList = new ArrayList<>();
@@ -201,10 +204,11 @@
     /**
      * Init instance of MultiSimSettingController.
      */
-    public static MultiSimSettingController init(Context context) {
+    public static MultiSimSettingController init(Context context,
+            @NonNull FeatureFlags featureFlags) {
         synchronized (MultiSimSettingController.class) {
             if (sInstance == null) {
-                sInstance = new MultiSimSettingController(context);
+                sInstance = new MultiSimSettingController(context, featureFlags);
             } else {
                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
             }
@@ -213,9 +217,10 @@
     }
 
     @VisibleForTesting
-    public MultiSimSettingController(Context context) {
+    public MultiSimSettingController(Context context, @NonNull FeatureFlags featureFlags) {
         mContext = context;
         mSubscriptionManagerService = SubscriptionManagerService.getInstance();
+        mFeatureFlags = featureFlags;
 
         // Initialize mCarrierConfigLoadedSubIds and register to listen to carrier config change.
         TelephonyManager telephonyManager = ((TelephonyManager) mContext.getSystemService(
@@ -239,6 +244,24 @@
                         onCarrierConfigChanged(slotIndex, subId));
     }
 
+    private boolean hasCalling() {
+        if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true;
+        return mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_CALLING);
+    }
+
+    private boolean hasData() {
+        if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true;
+        return mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_DATA);
+    }
+
+    private boolean hasMessaging() {
+        if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true;
+        return mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_MESSAGING);
+    }
+
     /**
      * Notify MOBILE_DATA of a subscription is changed.
      */
@@ -606,35 +629,43 @@
                 || mActiveModemCount == 1)) {
             int subId = mPrimarySubList.get(0);
             if (DBG) log("updateDefaultValues: to only primary sub " + subId);
-            mSubscriptionManagerService.setDefaultDataSubId(subId);
-            mSubscriptionManagerService.setDefaultVoiceSubId(subId);
-            mSubscriptionManagerService.setDefaultSmsSubId(subId);
+            if (hasData()) mSubscriptionManagerService.setDefaultDataSubId(subId);
+            if (hasCalling()) mSubscriptionManagerService.setDefaultVoiceSubId(subId);
+            if (hasMessaging()) mSubscriptionManagerService.setDefaultSmsSubId(subId);
             sendDefaultSubConfirmedNotification(subId);
             return;
         }
 
         if (DBG) log("updateDefaultValues: records: " + mPrimarySubList);
 
-        boolean dataSelected, voiceSelected, smsSelected;
+        boolean dataSelected = false;
+        boolean voiceSelected = false;
+        boolean smsSelected = false;
 
-        // Update default data subscription.
-        if (DBG) log("updateDefaultValues: Update default data subscription");
-        dataSelected = updateDefaultValue(mPrimarySubList,
-                mSubscriptionManagerService.getDefaultDataSubId(),
-                mSubscriptionManagerService::setDefaultDataSubId);
+        if (hasData()) {
+            // Update default data subscription.
+            if (DBG) log("updateDefaultValues: Update default data subscription");
+            dataSelected = updateDefaultValue(mPrimarySubList,
+                    mSubscriptionManagerService.getDefaultDataSubId(),
+                    mSubscriptionManagerService::setDefaultDataSubId);
+        }
 
-        // Update default voice subscription.
-        if (DBG) log("updateDefaultValues: Update default voice subscription");
-        voiceSelected = updateDefaultValue(mPrimarySubList,
-                mSubscriptionManagerService.getDefaultVoiceSubId(),
-                mSubscriptionManagerService::setDefaultVoiceSubId);
+        if (hasCalling()) {
+            // Update default voice subscription.
+            if (DBG) log("updateDefaultValues: Update default voice subscription");
+            voiceSelected = updateDefaultValue(mPrimarySubList,
+                    mSubscriptionManagerService.getDefaultVoiceSubId(),
+                    mSubscriptionManagerService::setDefaultVoiceSubId);
+        }
 
-        // Update default sms subscription.
-        if (DBG) log("updateDefaultValues: Update default sms subscription");
-        smsSelected = updateDefaultValue(mPrimarySubList,
-                mSubscriptionManagerService.getDefaultSmsSubId(),
-                mSubscriptionManagerService::setDefaultSmsSubId,
-                mIsAskEverytimeSupportedForSms);
+        if (hasMessaging()) {
+            // Update default sms subscription.
+            if (DBG) log("updateDefaultValues: Update default sms subscription");
+            smsSelected = updateDefaultValue(mPrimarySubList,
+                    mSubscriptionManagerService.getDefaultSmsSubId(),
+                    mSubscriptionManagerService::setDefaultSmsSubId,
+                    mIsAskEverytimeSupportedForSms);
+        }
 
         boolean autoFallbackEnabled = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_voice_data_sms_auto_fallback);
@@ -1023,11 +1054,11 @@
 
         int autoDefaultSubId = primarySubList.get(0);
 
-        if ((primarySubList.size() == 1) && !smsSelected) {
+        if (hasMessaging() && (primarySubList.size() == 1) && !smsSelected) {
             mSubscriptionManagerService.setDefaultSmsSubId(autoDefaultSubId);
         }
 
-        if ((primarySubList.size() == 1) && !voiceSelected) {
+        if (hasCalling() && (primarySubList.size() == 1) && !voiceSelected) {
             mSubscriptionManagerService.setDefaultVoiceSubId(autoDefaultSubId);
         }
 
@@ -1036,13 +1067,15 @@
         log("User pref subId = " + userPrefDataSubId + " current dds " + defaultDataSubId
                 + " next active subId " + autoDefaultSubId);
 
-        // If earlier user selected DDS is now available, set that as DDS subId.
-        if (primarySubList.contains(userPrefDataSubId)
-                && SubscriptionManager.isValidSubscriptionId(userPrefDataSubId)
-                && (defaultDataSubId != userPrefDataSubId)) {
-            mSubscriptionManagerService.setDefaultDataSubId(userPrefDataSubId);
-        } else if (!dataSelected) {
-            mSubscriptionManagerService.setDefaultDataSubId(autoDefaultSubId);
+        if (hasData()) {
+            // If earlier user selected DDS is now available, set that as DDS subId.
+            if (primarySubList.contains(userPrefDataSubId)
+                    && SubscriptionManager.isValidSubscriptionId(userPrefDataSubId)
+                    && (defaultDataSubId != userPrefDataSubId)) {
+                mSubscriptionManagerService.setDefaultDataSubId(userPrefDataSubId);
+            } else if (!dataSelected) {
+                mSubscriptionManagerService.setDefaultDataSubId(autoDefaultSubId);
+            }
         }
 
         if (DBG) {
diff --git a/src/java/com/android/internal/telephony/NetworkTypeController.java b/src/java/com/android/internal/telephony/NetworkTypeController.java
index b9ad388..67ca1e1 100644
--- a/src/java/com/android/internal/telephony/NetworkTypeController.java
+++ b/src/java/com/android/internal/telephony/NetworkTypeController.java
@@ -26,6 +26,7 @@
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.PowerManager;
+import android.os.SystemClock;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation;
 import android.telephony.CarrierConfigManager;
@@ -194,6 +195,7 @@
     private boolean mIsPhysicalChannelConfigOn;
     private boolean mIsPrimaryTimerActive;
     private boolean mIsSecondaryTimerActive;
+    private long mSecondaryTimerExpireTimestamp;
     private boolean mIsTimerResetEnabledForLegacyStateRrcIdle;
     /** Carrier config to reset timers when mccmnc changes */
     private boolean mIsTimerResetEnabledOnPlmnChanges;
@@ -220,6 +222,7 @@
 
     // Cached copies below to prevent race conditions
     @NonNull private ServiceState mServiceState;
+    /** Used to track link status to be DORMANT or ACTIVE */
     @Nullable private List<PhysicalChannelConfig> mPhysicalChannelConfigs;
 
     // Ratchet physical channel config fields to prevent 5G/5G+ flickering
@@ -378,6 +381,9 @@
         createTimerRules(nrIconConfiguration, overrideTimerRule, overrideSecondaryTimerRule);
         updatePhysicalChannelConfigs(
                 mPhone.getServiceStateTracker().getPhysicalChannelConfigList());
+        if (isUsingPhysicalChannelConfigForRrcDetection()) {
+            mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
+        }
     }
 
     private void createTimerRules(String icons, String timers, String secondaryTimers) {
@@ -663,6 +669,7 @@
                 case EVENT_SECONDARY_TIMER_EXPIRED:
                     if (DBG) log("Secondary timer expired for state: " + mSecondaryTimerState);
                     mIsSecondaryTimerActive = false;
+                    mSecondaryTimerExpireTimestamp = 0;
                     mSecondaryTimerState = "";
                     updateTimers();
                     mLastShownNrDueToAdvancedBand = false;
@@ -1035,11 +1042,15 @@
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                     }
-                    // Check NR advanced in case NR advanced bands were added
-                    if (isNrAdvanced()) {
-                        transitionTo(mNrConnectedAdvancedState);
-                    } else if (isPhysicalLinkActive()) {
-                        transitionWithTimerTo(mNrConnectedState);
+                    if (isPhysicalLinkActive()) {
+                        if (isNrAdvanced()) {
+                            transitionTo(mNrConnectedAdvancedState);
+                        } else {
+                            transitionWithTimerTo(mNrConnectedState);
+                        }
+                    } else {
+                        // Update in case the override network type changed
+                        updateOverrideNetworkType();
                     }
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
@@ -1113,11 +1124,10 @@
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                     }
-                    // Check NR advanced in case NR advanced bands were added
-                    if (isNrAdvanced()) {
-                        transitionTo(mNrConnectedAdvancedState);
-                    } else if (!isPhysicalLinkActive() && mFeatureFlags.supportNrSaRrcIdle()) {
+                    if (!isPhysicalLinkActive() && mFeatureFlags.supportNrSaRrcIdle()) {
                         transitionWithTimerTo(mNrIdleState);
+                    } else if (isNrAdvanced()) {
+                        transitionTo(mNrConnectedAdvancedState);
                     }
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
@@ -1203,11 +1213,10 @@
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                     }
-                    // Check NR advanced in case NR advanced bands were removed
-                    if (!isNrAdvanced()) {
-                        transitionWithTimerTo(isPhysicalLinkActive()
-                                || !mFeatureFlags.supportNrSaRrcIdle()
-                                ? mNrConnectedState : mNrIdleState);
+                    if (!isPhysicalLinkActive() && mFeatureFlags.supportNrSaRrcIdle()) {
+                        transitionWithTimerTo(mNrIdleState);
+                    } else if (!isNrAdvanced()) {
+                        transitionWithTimerTo(mNrConnectedState);
                     }
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
@@ -1246,6 +1255,8 @@
     private void updatePhysicalChannelConfigs(List<PhysicalChannelConfig> physicalChannelConfigs) {
         boolean isPccListEmpty = physicalChannelConfigs == null || physicalChannelConfigs.isEmpty();
         if (isPccListEmpty && isUsingPhysicalChannelConfigForRrcDetection()) {
+            // Clear mPrimaryCellChangedWhileIdle to allow later potential one-off PCI change.
+            // Update link status to be DORMANT, but keep ratcheted bands.
             log("Physical channel configs updated: not updating PCC fields for empty PCC list "
                     + "indicating RRC idle.");
             mPrimaryCellChangedWhileIdle = false;
@@ -1299,12 +1310,13 @@
         } else {
             if (mFeatureFlags.supportNrSaRrcIdle() && mDoesPccListIndicateIdle
                     && isUsingPhysicalChannelConfigForRrcDetection()
-                    && !mPrimaryCellChangedWhileIdle && isTimerActiveForRrcIdle()
+                    && !mPrimaryCellChangedWhileIdle
                     && !isNrAdvancedForPccFields(nrBandwidths, nrBands)) {
-                log("Allow primary cell change during RRC idle timer without changing state: "
+                log("Allow primary cell change once during RRC idle without changing state: "
                         + mLastAnchorNrCellId + " -> " + anchorNrCellId);
                 mPrimaryCellChangedWhileIdle = true;
                 mLastAnchorNrCellId = anchorNrCellId;
+                reduceSecondaryTimerIfNeeded();
                 return;
             }
             if (mRatchetPccFieldsForSameAnchorNrCell) {
@@ -1325,17 +1337,45 @@
         }
     }
 
+    /**
+     * Called when PCI change, specifically during idle state.
+     */
+    private void reduceSecondaryTimerIfNeeded() {
+        if (!mIsSecondaryTimerActive || mNrAdvancedBandsSecondaryTimer <= 0) return;
+        // Secondary timer is active, so we must have a valid secondary rule right now.
+        OverrideTimerRule secondaryRule = mOverrideTimerRules.get(mPrimaryTimerState);
+        if (secondaryRule != null) {
+            int secondaryDuration = secondaryRule.getSecondaryTimer(mSecondaryTimerState);
+            long durationMillis = secondaryDuration * 1000L;
+            if ((mSecondaryTimerExpireTimestamp - SystemClock.uptimeMillis()) > durationMillis) {
+                if (DBG) log("Due to PCI change, reduce the secondary timer to " + durationMillis);
+                removeMessages(EVENT_SECONDARY_TIMER_EXPIRED);
+                sendMessageDelayed(EVENT_SECONDARY_TIMER_EXPIRED, mSecondaryTimerState,
+                        durationMillis);
+            }
+        } else {
+            loge("!! Secondary timer is active, but found no rule for " + mPrimaryTimerState);
+        }
+    }
+
     private void transitionWithTimerTo(IState destState) {
         String destName = destState.getName();
-        if (DBG) log("Transition with primary timer from " + mPreviousState + " to " + destName);
-        OverrideTimerRule rule = mOverrideTimerRules.get(mPreviousState);
-        if (!mIsDeviceIdleMode && rule != null && rule.getTimer(destName) > 0) {
-            int duration = rule.getTimer(destName);
-            if (DBG) log(duration + "s primary timer started for state: " + mPreviousState);
-            mPrimaryTimerState = mPreviousState;
-            mPreviousState = getCurrentState().getName();
-            mIsPrimaryTimerActive = true;
-            sendMessageDelayed(EVENT_PRIMARY_TIMER_EXPIRED, destState, duration * 1000L);
+        if (mIsPrimaryTimerActive) {
+            log("Transition without timer from " + getCurrentState().getName() + " to " + destName
+                    + " due to existing " + mPrimaryTimerState + " primary timer.");
+        } else {
+            if (DBG) {
+                log("Transition with primary timer from " + mPreviousState + " to " + destName);
+            }
+            OverrideTimerRule rule = mOverrideTimerRules.get(mPreviousState);
+            if (!mIsDeviceIdleMode && rule != null && rule.getTimer(destName) > 0) {
+                int duration = rule.getTimer(destName);
+                if (DBG) log(duration + "s primary timer started for state: " + mPreviousState);
+                mPrimaryTimerState = mPreviousState;
+                mPreviousState = getCurrentState().getName();
+                mIsPrimaryTimerActive = true;
+                sendMessageDelayed(EVENT_PRIMARY_TIMER_EXPIRED, destState, duration * 1000L);
+            }
         }
         transitionTo(destState);
     }
@@ -1357,7 +1397,9 @@
             mSecondaryTimerState = currentName;
             mPreviousState = currentName;
             mIsSecondaryTimerActive = true;
-            sendMessageDelayed(EVENT_SECONDARY_TIMER_EXPIRED, destState, duration * 1000L);
+            long durationMillis = duration * 1000L;
+            mSecondaryTimerExpireTimestamp = SystemClock.uptimeMillis() + durationMillis;
+            sendMessageDelayed(EVENT_SECONDARY_TIMER_EXPIRED, destState, durationMillis);
         }
         mIsPrimaryTimerActive = false;
         transitionTo(getCurrentState());
@@ -1367,14 +1409,12 @@
         int dataRat = getDataNetworkType();
         IState transitionState;
         if (dataRat == TelephonyManager.NETWORK_TYPE_NR || (isLte(dataRat) && isNrConnected())) {
-            if (isNrAdvanced()) {
+            if (!isPhysicalLinkActive() && mFeatureFlags.supportNrSaRrcIdle()) {
+                transitionState = mNrIdleState;
+            } else if (isNrAdvanced()) {
                 transitionState = mNrConnectedAdvancedState;
             } else {
-                if (isPhysicalLinkActive() || !mFeatureFlags.supportNrSaRrcIdle()) {
-                    transitionState = mNrConnectedState;
-                } else {
-                    transitionState = mNrIdleState;
-                }
+                transitionState = mNrConnectedState;
             }
         } else if (isLte(dataRat) && isNrNotRestricted()) {
             if (isPhysicalLinkActive()) {
@@ -1403,13 +1443,11 @@
 
         String currentState = getCurrentState().getName();
 
-        if (mIsPrimaryTimerActive && getOverrideNetworkType() == getCurrentOverrideNetworkType()
-                && getDataNetworkType()
-                == mDisplayInfoController.getTelephonyDisplayInfo().getNetworkType()) {
-            // remove primary timer if device goes back to the original icon
+        if (mIsPrimaryTimerActive && mPrimaryTimerState.equals(currentState)) {
+            // remove primary timer if device goes back to the original state
             if (DBG) {
-                log("Remove primary timer since icon of primary state and current icon equal: "
-                        + mPrimaryTimerState);
+                log("Remove primary timer since primary timer state ("
+                        + mPrimaryTimerState + ") was reestablished.");
             }
             removeMessages(EVENT_PRIMARY_TIMER_EXPIRED);
             mIsPrimaryTimerActive = false;
@@ -1426,6 +1464,7 @@
             }
             removeMessages(EVENT_SECONDARY_TIMER_EXPIRED);
             mIsSecondaryTimerActive = false;
+            mSecondaryTimerExpireTimestamp = 0;
             mSecondaryTimerState = "";
             transitionToCurrentState();
             return;
@@ -1435,10 +1474,11 @@
             if (currentState.equals(STATE_CONNECTED_NR_ADVANCED)) {
                 if (DBG) log("Reset timers since state is NR_ADVANCED.");
                 resetAllTimers();
-            } else if (currentState.equals(STATE_CONNECTED)
+            } else if ((currentState.equals(STATE_CONNECTED)
+                    || currentState.equals(STATE_CONNECTED_RRC_IDLE))
                     && !mPrimaryTimerState.equals(STATE_CONNECTED_NR_ADVANCED)
                     && !mSecondaryTimerState.equals(STATE_CONNECTED_NR_ADVANCED)) {
-                if (DBG) log("Reset non-NR_ADVANCED timers since state is NR_CONNECTED");
+                if (DBG) log("Reset non-NR advanced timers since state is NR connected/idle");
                 resetAllTimers();
             } else {
                 int rat = getDataNetworkType();
@@ -1455,24 +1495,13 @@
         removeMessages(EVENT_SECONDARY_TIMER_EXPIRED);
         mIsPrimaryTimerActive = false;
         mIsSecondaryTimerActive = false;
+        mSecondaryTimerExpireTimestamp = 0;
         mPrimaryTimerState = "";
         mSecondaryTimerState = "";
 
         mLastShownNrDueToAdvancedBand = false;
     }
 
-    private boolean isTimerActiveForRrcIdle() {
-        if (mIsPrimaryTimerActive) {
-            return mPrimaryTimerState.equals(STATE_CONNECTED_RRC_IDLE)
-                    || mPrimaryTimerState.equals(STATE_NOT_RESTRICTED_RRC_IDLE);
-        } else if (mIsSecondaryTimerActive) {
-            return mSecondaryTimerState.equals(STATE_CONNECTED_RRC_IDLE)
-                    || mSecondaryTimerState.equals(STATE_NOT_RESTRICTED_RRC_IDLE);
-        } else {
-            return false;
-        }
-    }
-
     /**
      * Private class defining timer rules between states to prevent flickering. These rules are
      * created in {@link #parseCarrierConfigs()} based on various carrier configs.
diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java
index c5bc428..803fb19 100644
--- a/src/java/com/android/internal/telephony/PhoneFactory.java
+++ b/src/java/com/android/internal/telephony/PhoneFactory.java
@@ -212,7 +212,7 @@
                         Looper.myLooper(), featureFlags);
 
                 TelephonyComponentFactory.getInstance().inject(MultiSimSettingController.class.
-                        getName()).initMultiSimSettingController(context);
+                        getName()).initMultiSimSettingController(context, featureFlags);
 
                 if (context.getPackageManager().hasSystemFeature(
                         PackageManager.FEATURE_TELEPHONY_EUICC)) {
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index 5177adb..8b3be1e 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -31,6 +31,7 @@
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.hardware.radio.V1_0.IRadio;
 import android.hardware.radio.V1_0.RadioError;
 import android.hardware.radio.V1_0.RadioIndicationType;
@@ -271,6 +272,13 @@
 
     static final String[] HIDL_SERVICE_NAME = {"slot1", "slot2", "slot3"};
 
+    private static final Map<String, Integer> FEATURES_TO_SERVICES = Map.ofEntries(
+            Map.entry(PackageManager.FEATURE_TELEPHONY_CALLING, HAL_SERVICE_VOICE),
+            Map.entry(PackageManager.FEATURE_TELEPHONY_DATA, HAL_SERVICE_DATA),
+            Map.entry(PackageManager.FEATURE_TELEPHONY_MESSAGING, HAL_SERVICE_MESSAGING),
+            Map.entry(PackageManager.FEATURE_TELEPHONY_IMS, HAL_SERVICE_IMS)
+    );
+
     public static List<TelephonyHistogram> getTelephonyRILTimingHistograms() {
         List<TelephonyHistogram> list;
         synchronized (sRilTimeHistograms) {
@@ -621,6 +629,7 @@
             if (mDisabledRadioServices.get(HAL_SERVICE_RADIO).contains(mPhoneId)) {
                 riljLoge("getRadioProxy: mRadioProxy for " + HIDL_SERVICE_NAME[mPhoneId]
                         + " is disabled");
+                return null;
             } else {
                 try {
                     mRadioProxy = android.hardware.radio.V1_6.IRadio.getService(
@@ -662,6 +671,7 @@
                     mDisabledRadioServices.get(HAL_SERVICE_RADIO).add(mPhoneId);
                     riljLoge("getRadioProxy: set mRadioProxy for "
                             + HIDL_SERVICE_NAME[mPhoneId] + " as disabled");
+                    return null;
                 }
             }
         } catch (RemoteException e) {
@@ -718,6 +728,9 @@
     public synchronized RadioServiceProxy getRadioServiceProxy(int service) {
         if (!SubscriptionManager.isValidPhoneId(mPhoneId)) return mServiceProxies.get(service);
         if ((service >= HAL_SERVICE_IMS) && !isRadioServiceSupported(service)) {
+            riljLogw("getRadioServiceProxy: " + serviceToString(service) + " for "
+                    + HIDL_SERVICE_NAME[mPhoneId] + " is not supported\n"
+                    + android.util.Log.getStackTraceString(new RuntimeException()));
             return mServiceProxies.get(service);
         }
         if (!mIsCellularSupported) {
@@ -733,7 +746,9 @@
         try {
             if (mMockModem == null && mDisabledRadioServices.get(service).contains(mPhoneId)) {
                 riljLoge("getRadioServiceProxy: " + serviceToString(service) + " for "
-                        + HIDL_SERVICE_NAME[mPhoneId] + " is disabled");
+                        + HIDL_SERVICE_NAME[mPhoneId] + " is disabled\n"
+                        + android.util.Log.getStackTraceString(new RuntimeException()));
+                return null;
             } else {
                 IBinder binder;
                 switch (service) {
@@ -944,7 +959,8 @@
                     mDisabledRadioServices.get(service).add(mPhoneId);
                     mHalVersion.put(service, RADIO_HAL_VERSION_UNKNOWN);
                     riljLoge("getRadioServiceProxy: set " + serviceToString(service) + " for "
-                            + HIDL_SERVICE_NAME[mPhoneId] + " as disabled");
+                            + HIDL_SERVICE_NAME[mPhoneId] + " as disabled\n"
+                            + android.util.Log.getStackTraceString(new RuntimeException()));
                 }
             }
         } catch (RemoteException e) {
@@ -1094,9 +1110,16 @@
             tdc.registerRIL(this);
         }
 
+        validateFeatureFlags();
+
         // Set radio callback; needed to set RadioIndication callback (should be done after
         // wakelock stuff is initialized above as callbacks are received on separate binder threads)
         for (int service = MIN_SERVICE_IDX; service <= MAX_SERVICE_IDX; service++) {
+            if (!isRadioServiceSupported(service)) {
+                riljLog("Not initializing " + serviceToString(service) + " (not supported)");
+                continue;
+            }
+
             if (service == HAL_SERVICE_RADIO) {
                 getRadioProxy();
             } else {
@@ -1161,6 +1184,26 @@
         return false;
     }
 
+    private void validateFeatureFlags() {
+        PackageManager pm = mContext.getPackageManager();
+        for (var entry : FEATURES_TO_SERVICES.entrySet()) {
+            String feature = entry.getKey();
+            int service = entry.getValue();
+
+            boolean hasFeature = pm.hasSystemFeature(feature);
+            boolean hasService = isRadioServiceSupported(service);
+
+            if (hasFeature && !hasService) {
+                riljLoge("Feature " + feature + " is declared, but service "
+                        + serviceToString(service) + " is missing");
+            }
+            if (!hasFeature && hasService) {
+                riljLoge("Service " + serviceToString(service) + " is available, but feature "
+                        + feature + " is not declared");
+            }
+        }
+    }
+
     private boolean isRadioBugDetectionEnabled() {
         return Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.ENABLE_RADIO_BUG_DETECTION, 1) != 0;
diff --git a/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java b/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java
index bab4d12..4d9196e 100644
--- a/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java
+++ b/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java
@@ -86,6 +86,13 @@
         register();
     }
 
+    private void requestCapabilities() {
+        if (mRadioInterfaceCapabilities != null) return;
+
+        mRadioConfig.getHalDeviceCapabilities(obtainMessage(
+                EVENT_GET_HAL_DEVICE_CAPABILITIES_DONE));
+    }
+
     /**
      * Gets the radio interface capabilities for the device
      */
@@ -95,8 +102,7 @@
             // Only incur cost of synchronization block if mRadioInterfaceCapabilities isn't null
             synchronized (mLockRadioInterfaceCapabilities) {
                 if (mRadioInterfaceCapabilities == null) {
-                    mRadioConfig.getHalDeviceCapabilities(
-                            obtainMessage(EVENT_GET_HAL_DEVICE_CAPABILITIES_DONE));
+                    requestCapabilities();
                     try {
                         if (Looper.myLooper() != getLooper()) {
                             mLockRadioInterfaceCapabilities.wait(2000);
@@ -158,7 +164,7 @@
         switch (msg.what) {
             case Phone.EVENT_RADIO_AVAILABLE:
             case Phone.EVENT_RADIO_ON:
-                getCapabilities();
+                requestCapabilities();
                 break;
             case EVENT_GET_HAL_DEVICE_CAPABILITIES_DONE:
                 setupCapabilities((AsyncResult) msg.obj);
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index 04f5c08..498535b 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -1013,10 +1013,12 @@
      * Notifies the {@link SmsDispatchersController} that sending MO SMS is failed.
      *
      * @param tracker holds the SMS message to be sent
+     * @param isOverIms a flag specifying whether SMS is sent via IMS or not
      */
-    protected void notifySmsSentFailedToEmergencyStateTracker(SmsTracker tracker) {
+    protected void notifySmsSentFailedToEmergencyStateTracker(SmsTracker tracker,
+            boolean isOverIms) {
         mSmsDispatchersController.notifySmsSentFailedToEmergencyStateTracker(
-                tracker.mDestAddress, tracker.mMessageId);
+                tracker.mDestAddress, tracker.mMessageId, isOverIms);
     }
 
     /**
@@ -1052,7 +1054,7 @@
             tracker.onSent(mContext);
             mPhone.notifySmsSent(tracker.mDestAddress);
             mSmsDispatchersController.notifySmsSentToEmergencyStateTracker(
-                    tracker.mDestAddress, tracker.mMessageId);
+                    tracker.mDestAddress, tracker.mMessageId, false);
 
             mPhone.getSmsStats().onOutgoingSms(
                     tracker.mImsRetry > 0 /* isOverIms */,
@@ -1103,7 +1105,7 @@
             // if sms over IMS is not supported on data and voice is not available...
             if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
                 tracker.onFailed(mContext, getNotInServiceError(ss), NO_ERROR_CODE);
-                notifySmsSentFailedToEmergencyStateTracker(tracker);
+                notifySmsSentFailedToEmergencyStateTracker(tracker, false);
                 mPhone.getSmsStats().onOutgoingSms(
                         tracker.mImsRetry > 0 /* isOverIms */,
                         SmsConstants.FORMAT_3GPP2.equals(getFormat()),
@@ -1164,7 +1166,7 @@
             } else {
                 int errorCode = (smsResponse != null) ? smsResponse.mErrorCode : NO_ERROR_CODE;
                 tracker.onFailed(mContext, error, errorCode);
-                notifySmsSentFailedToEmergencyStateTracker(tracker);
+                notifySmsSentFailedToEmergencyStateTracker(tracker, false);
                 mPhone.getSmsStats().onOutgoingSms(
                         tracker.mImsRetry > 0 /* isOverIms */,
                         SmsConstants.FORMAT_3GPP2.equals(getFormat()),
@@ -2389,7 +2391,7 @@
             int errorCode) {
         for (SmsTracker tracker : trackers) {
             tracker.onFailed(mContext, error, errorCode);
-            notifySmsSentFailedToEmergencyStateTracker(tracker);
+            notifySmsSentFailedToEmergencyStateTracker(tracker, false);
         }
         if (trackers.length > 0) {
             // This error occurs before the SMS is sent. Make an assumption if it would have
diff --git a/src/java/com/android/internal/telephony/SmsDispatchersController.java b/src/java/com/android/internal/telephony/SmsDispatchersController.java
index 8795840..cc287f8 100644
--- a/src/java/com/android/internal/telephony/SmsDispatchersController.java
+++ b/src/java/com/android/internal/telephony/SmsDispatchersController.java
@@ -109,6 +109,9 @@
     /** Called when AP domain selection is abnormally terminated. */
     private static final int EVENT_DOMAIN_SELECTION_TERMINATED_ABNORMALLY = 20;
 
+    /** Called when MT SMS is received via IMS. */
+    private static final int EVENT_SMS_RECEIVED_VIA_IMS = 21;
+
     /** Delete any partial message segments after being IN_SERVICE for 1 day. */
     private static final long PARTIAL_SEGMENT_WAIT_DURATION = (long) (60 * 60 * 1000) * 24;
     /** Constant for invalid time */
@@ -487,8 +490,10 @@
                 String destAddr = (String) args.arg1;
                 Long messageId = (Long) args.arg2;
                 Boolean success = (Boolean) args.arg3;
+                Boolean isOverIms = (Boolean) args.arg4;
                 try {
-                    handleSmsSentCompletedUsingDomainSelection(destAddr, messageId, success);
+                    handleSmsSentCompletedUsingDomainSelection(
+                            destAddr, messageId, success, isOverIms);
                 } finally {
                     args.recycle();
                 }
@@ -499,6 +504,10 @@
                         (DomainSelectionConnectionHolder) msg.obj);
                 break;
             }
+            case EVENT_SMS_RECEIVED_VIA_IMS: {
+                handleSmsReceivedViaIms((String) msg.obj);
+                break;
+            }
             default:
                 if (isCdmaMo()) {
                     mCdmaDispatcher.handleMessage(msg);
@@ -808,7 +817,7 @@
                 Rlog.e(TAG, "sendRetrySms failed to re-encode per missing fields!");
                 tracker.onFailed(mContext, SmsManager.RESULT_SMS_SEND_RETRY_FAILED, NO_ERROR_CODE);
                 notifySmsSentFailedToEmergencyStateTracker(
-                        tracker.mDestAddress, tracker.mMessageId);
+                        tracker.mDestAddress, tracker.mMessageId, !retryUsingImsService);
                 return;
             }
             String scAddr = (String) map.get("scAddr");
@@ -817,7 +826,7 @@
                 Rlog.e(TAG, "sendRetrySms failed due to null destAddr");
                 tracker.onFailed(mContext, SmsManager.RESULT_SMS_SEND_RETRY_FAILED, NO_ERROR_CODE);
                 notifySmsSentFailedToEmergencyStateTracker(
-                        tracker.mDestAddress, tracker.mMessageId);
+                        tracker.mDestAddress, tracker.mMessageId, !retryUsingImsService);
                 return;
             }
 
@@ -859,7 +868,7 @@
                         + "destPort: %s", scAddr, map.get("destPort")));
                 tracker.onFailed(mContext, SmsManager.RESULT_SMS_SEND_RETRY_FAILED, NO_ERROR_CODE);
                 notifySmsSentFailedToEmergencyStateTracker(
-                        tracker.mDestAddress, tracker.mMessageId);
+                        tracker.mDestAddress, tracker.mMessageId, !retryUsingImsService);
                 return;
             }
             // replace old smsc and pdu with newly encoded ones
@@ -1147,13 +1156,16 @@
      * @param destAddr The destination address for SMS.
      * @param messageId The message id for SMS.
      * @param success A flag specifying whether MO SMS is successfully sent or not.
+     * @param isOverIms A flag specifying whether MO SMS is sent over IMS or not.
      */
     private void handleSmsSentCompletedUsingDomainSelection(@NonNull String destAddr,
-            long messageId, boolean success) {
+            long messageId, boolean success, boolean isOverIms) {
         if (mEmergencyStateTracker != null) {
             TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
             if (tm.isEmergencyNumber(destAddr)) {
-                mEmergencyStateTracker.endSms(String.valueOf(messageId), success);
+                mEmergencyStateTracker.endSms(String.valueOf(messageId), success,
+                        isOverIms ? NetworkRegistrationInfo.DOMAIN_PS
+                                  : NetworkRegistrationInfo.DOMAIN_CS);
             }
         }
     }
@@ -1161,13 +1173,15 @@
     /**
      * Called when MO SMS is successfully sent.
      */
-    protected void notifySmsSentToEmergencyStateTracker(@NonNull String destAddr, long messageId) {
+    protected void notifySmsSentToEmergencyStateTracker(@NonNull String destAddr, long messageId,
+            boolean isOverIms) {
         if (isSmsDomainSelectionEnabled()) {
             // Run on main thread for interworking with EmergencyStateTracker.
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = destAddr;
             args.arg2 = Long.valueOf(messageId);
             args.arg3 = Boolean.TRUE;
+            args.arg4 = Boolean.valueOf(isOverIms);
             sendMessage(obtainMessage(EVENT_SMS_SENT_COMPLETED_USING_DOMAIN_SELECTION, args));
         }
     }
@@ -1176,17 +1190,42 @@
      * Called when sending MO SMS is failed.
      */
     protected void notifySmsSentFailedToEmergencyStateTracker(@NonNull String destAddr,
-            long messageId) {
+            long messageId, boolean isOverIms) {
         if (isSmsDomainSelectionEnabled()) {
             // Run on main thread for interworking with EmergencyStateTracker.
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = destAddr;
             args.arg2 = Long.valueOf(messageId);
             args.arg3 = Boolean.FALSE;
+            args.arg4 = Boolean.valueOf(isOverIms);
             sendMessage(obtainMessage(EVENT_SMS_SENT_COMPLETED_USING_DOMAIN_SELECTION, args));
         }
     }
 
+    /**
+     * Called when MT SMS is received via IMS.
+     *
+     * @param origAddr The originating address of MT SMS.
+     */
+    private void handleSmsReceivedViaIms(@Nullable String origAddr) {
+        if (mEmergencyStateTracker != null) {
+            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+            if (origAddr != null && tm.isEmergencyNumber(origAddr)) {
+                mEmergencyStateTracker.onEmergencySmsReceived();
+            }
+        }
+    }
+
+    /**
+     * Called when MT SMS is received via IMS.
+     */
+    protected void notifySmsReceivedViaImsToEmergencyStateTracker(@Nullable String origAddr) {
+        if (isSmsDomainSelectionEnabled()) {
+            // Run on main thread for interworking with EmergencyStateTracker.
+            sendMessage(obtainMessage(EVENT_SMS_RECEIVED_VIA_IMS, origAddr));
+        }
+    }
+
     private boolean isTestEmergencyNumber(String number) {
         try {
             TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
index f5aa074..5da4b12 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -305,8 +305,9 @@
     /**
      * Create a new EmergencyNumberTracker.
      */
-    public EmergencyNumberTracker makeEmergencyNumberTracker(Phone phone, CommandsInterface ci) {
-        return new EmergencyNumberTracker(phone, ci);
+    public EmergencyNumberTracker makeEmergencyNumberTracker(Phone phone, CommandsInterface ci,
+            @NonNull FeatureFlags featureFlags) {
+        return new EmergencyNumberTracker(phone, ci, featureFlags);
     }
 
     private static final boolean USE_NEW_NITZ_STATE_MACHINE = true;
@@ -507,8 +508,9 @@
      * @param c The context.
      * @return The multi sim settings controller instance.
      */
-    public MultiSimSettingController initMultiSimSettingController(Context c) {
-        return MultiSimSettingController.init(c);
+    public MultiSimSettingController initMultiSimSettingController(Context c,
+            @NonNull FeatureFlags featureFlags) {
+        return MultiSimSettingController.init(c, featureFlags);
     }
 
     /**
@@ -571,9 +573,11 @@
      * @return The data settings manager instance.
      */
     public @NonNull DataSettingsManager makeDataSettingsManager(@NonNull Phone phone,
-            @NonNull DataNetworkController dataNetworkController, @NonNull Looper looper,
+            @NonNull DataNetworkController dataNetworkController,
+            @NonNull FeatureFlags featureFlags, @NonNull Looper looper,
             @NonNull DataSettingsManager.DataSettingsManagerCallback callback) {
-        return new DataSettingsManager(phone, dataNetworkController, looper, callback);
+        return new DataSettingsManager(phone, dataNetworkController, featureFlags, looper,
+                callback);
     }
 
     /** Create CellularNetworkSecuritySafetySource. */
@@ -589,7 +593,8 @@
     }
 
     /** Create NullCipherNotifier. */
-    public NullCipherNotifier makeNullCipherNotifier() {
-        return NullCipherNotifier.getInstance();
+    public NullCipherNotifier makeNullCipherNotifier(
+            CellularNetworkSecuritySafetySource safetySource) {
+        return NullCipherNotifier.getInstance(safetySource);
     }
 }
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index 2119003..f40b6d6 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -129,7 +129,7 @@
         // if sms over IMS is not supported on data and voice is not available...
         if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
             tracker.onFailed(mContext, getNotInServiceError(ss), NO_ERROR_CODE);
-            notifySmsSentFailedToEmergencyStateTracker(tracker);
+            notifySmsSentFailedToEmergencyStateTracker(tracker, false);
             return;
         }
 
diff --git a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
index 02c459a..343bb0b 100644
--- a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
+++ b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
@@ -188,6 +188,12 @@
      * even if ping test fails.
      */
     private boolean mRequirePingTestBeforeSwitch = true;
+    /**
+     * TODO: remove after V.
+     * To indicate whether allow using roaming nDDS if user enabled its roaming when the DDS is not
+     * usable(OOS or disabled roaming)
+     */
+    private boolean mAllowNddsRoamning = true;
     /** The count of consecutive auto switch validation failure **/
     private int mAutoSwitchValidationFailedCount = 0;
     /**
@@ -444,6 +450,7 @@
         DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager();
         mScoreTolerance =  dataConfig.getAutoDataSwitchScoreTolerance();
         mRequirePingTestBeforeSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired();
+        mAllowNddsRoamning = dataConfig.doesAutoDataSwitchAllowRoaming();
         mAutoDataSwitchAvailabilityStabilityTimeThreshold =
                 dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold();
         mAutoDataSwitchPerformanceStabilityTimeThreshold =
@@ -694,7 +701,7 @@
             boolean isForPerformance = false;
             boolean needValidation = true;
 
-            if (sFeatureFlags.autoSwitchAllowRoaming()) {
+            if (isNddsRoamingEnabled()) {
                 if (mDefaultNetworkIsOnNonCellular) {
                     debugMessage.append(", back to default as default network")
                             .append(" is active on nonCellular transport");
@@ -820,7 +827,7 @@
             return invalidResult;
         }
 
-        if (sFeatureFlags.autoSwitchAllowRoaming()) {
+        if (isNddsRoamingEnabled()) {
             // check whether primary and secondary signal status are worth switching
             if (!isRatSignalStrengthBasedSwitchEnabled()
                     && isHomeService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) {
@@ -842,7 +849,7 @@
 
             Phone secondaryDataPhone = null;
             PhoneSignalStatus candidatePhoneStatus = mPhonesSignalStatus[phoneId];
-            if (sFeatureFlags.autoSwitchAllowRoaming()) {
+            if (isNddsRoamingEnabled()) {
                 PhoneSignalStatus.UsableState currentUsableState =
                         mPhonesSignalStatus[defaultPhoneId].getUsableState();
                 PhoneSignalStatus.UsableState candidateUsableState =
@@ -919,6 +926,13 @@
     }
 
     /**
+     * @return {@code true} If the feature of switching to roaming non DDS is enabled.
+     */
+    private boolean isNddsRoamingEnabled() {
+        return sFeatureFlags.autoDataSwitchAllowRoaming() && mAllowNddsRoamning;
+    }
+
+    /**
      * Called when the current environment suits auto data switch.
      * Start pre-switch validation if the current environment suits auto data switch for
      * {@link #mAutoDataSwitchAvailabilityStabilityTimeThreshold} MS.
diff --git a/src/java/com/android/internal/telephony/data/DataConfigManager.java b/src/java/com/android/internal/telephony/data/DataConfigManager.java
index 90743f8..0e06dad 100644
--- a/src/java/com/android/internal/telephony/data/DataConfigManager.java
+++ b/src/java/com/android/internal/telephony/data/DataConfigManager.java
@@ -1051,6 +1051,15 @@
     }
 
     /**
+     * TODO: remove after V.
+     * @return To indicate whether allow using roaming nDDS if user enabled its roaming when the DDS
+     * is not usable(OOS or disabled roaming)
+     */
+    public boolean doesAutoDataSwitchAllowRoaming() {
+        return mResources.getBoolean(com.android.internal.R.bool.auto_data_switch_allow_roaming);
+    }
+
+    /**
      * @return The maximum number of retries when a validation for switching failed.
      */
     public int getAutoDataSwitchValidationMaxRetry() {
diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java
index c1e6155..0dbbc5c 100644
--- a/src/java/com/android/internal/telephony/data/DataNetwork.java
+++ b/src/java/com/android/internal/telephony/data/DataNetwork.java
@@ -1180,12 +1180,14 @@
 
             mPhone.getServiceStateTracker().registerForCssIndicatorChanged(
                     getHandler(), EVENT_CSS_INDICATOR_CHANGED, null);
-            mPhone.getCallTracker().registerForVoiceCallStarted(
-                    getHandler(), EVENT_VOICE_CALL_STARTED, null);
-            mPhone.getCallTracker().registerForVoiceCallEnded(
-                    getHandler(), EVENT_VOICE_CALL_ENDED, null);
+            if (mPhone.getCallTracker() != null) {
+                mPhone.getCallTracker().registerForVoiceCallStarted(
+                        getHandler(), EVENT_VOICE_CALL_STARTED, null);
+                mPhone.getCallTracker().registerForVoiceCallEnded(
+                        getHandler(), EVENT_VOICE_CALL_ENDED, null);
+            }
             // Check null for devices not supporting FEATURE_TELEPHONY_IMS.
-            if (mPhone.getImsPhone() != null) {
+            if (mPhone.getImsPhone() != null && mPhone.getImsPhone().getCallTracker() != null) {
                 mPhone.getImsPhone().getCallTracker().registerForVoiceCallStarted(
                         getHandler(), EVENT_VOICE_CALL_STARTED, null);
                 mPhone.getImsPhone().getCallTracker().registerForVoiceCallEnded(
@@ -1223,12 +1225,14 @@
             }
 
             // Check null for devices not supporting FEATURE_TELEPHONY_IMS.
-            if (mPhone.getImsPhone() != null) {
+            if (mPhone.getImsPhone() != null && mPhone.getImsPhone().getCallTracker() != null) {
                 mPhone.getImsPhone().getCallTracker().unregisterForVoiceCallStarted(getHandler());
                 mPhone.getImsPhone().getCallTracker().unregisterForVoiceCallEnded(getHandler());
             }
-            mPhone.getCallTracker().unregisterForVoiceCallStarted(getHandler());
-            mPhone.getCallTracker().unregisterForVoiceCallEnded(getHandler());
+            if (mPhone.getCallTracker() != null) {
+                mPhone.getCallTracker().unregisterForVoiceCallStarted(getHandler());
+                mPhone.getCallTracker().unregisterForVoiceCallEnded(getHandler());
+            }
 
             mPhone.getServiceStateTracker().unregisterForCssIndicatorChanged(getHandler());
             TelephonyManager tm = mPhone.getContext().getSystemService(TelephonyManager.class);
@@ -1407,7 +1411,14 @@
                     } else {
                         loge("Failed to allocate PDU session id. e=" + ar.exception);
                     }
-                    setupData();
+                    //Check whether all network requests were removed before setupData.
+                    if (!mAttachedNetworkRequestList.isEmpty()) {
+                        setupData();
+                    } else {
+                        mRetryDelayMillis = DataCallResponse.RETRY_DURATION_UNDEFINED;
+                        mFailCause = DataFailCause.NO_RETRY_FAILURE;
+                        transitionTo(mDisconnectedState);
+                    }
                     break;
                 case EVENT_SETUP_DATA_NETWORK_RESPONSE:
                     int resultCode = msg.arg1;
@@ -1536,7 +1547,7 @@
                 //  For requests that can't be satisfied anymore, we need to put them back to the
                 //  unsatisfied pool. If none of network requests can be satisfied, then there is no
                 //  need to mark network agent connected. Just silently deactivate the data network.
-                if (mAttachedNetworkRequestList.size() == 0) {
+                if (mAttachedNetworkRequestList.isEmpty()) {
                     log("Tear down the network since there is no live network request.");
                     // Directly call onTearDown here. Calling tearDown will cause deadlock because
                     // EVENT_TEAR_DOWN_NETWORK is deferred until state machine enters connected
@@ -2507,7 +2518,8 @@
             newSuspendedState = true;
             // Check voice/data concurrency.
         } else if (!mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()
-                && mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
+                && mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+                && mPhone.getCallTracker() != null) {
             newSuspendedState = mPhone.getCallTracker().getState() != PhoneConstants.State.IDLE;
         }
 
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index 3ca28a3..70d3b23 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
 import android.net.NetworkPolicyManager;
@@ -426,6 +427,12 @@
         }
     };
 
+    private boolean hasCalling() {
+        if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true;
+        return mPhone.getContext().getPackageManager().hasSystemFeature(
+            PackageManager.FEATURE_TELEPHONY_CALLING);
+    }
+
     /**
      * The sorted network request list by priority. The highest priority network request stays at
      * the head of the list. The highest priority is 100, the lowest is 0.
@@ -861,7 +868,7 @@
 
         mDataSettingsManager = TelephonyComponentFactory.getInstance().inject(
                 DataSettingsManager.class.getName())
-                .makeDataSettingsManager(mPhone, this, looper,
+                .makeDataSettingsManager(mPhone, this, mFeatureFlags, looper,
                         new DataSettingsManagerCallback(this::post) {
                             @Override
                             public void onDataEnabledChanged(boolean enabled,
@@ -921,7 +928,7 @@
                             }
                         });
         mDataStallRecoveryManager = new DataStallRecoveryManager(mPhone, this, mDataServiceManagers
-                .get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN), looper,
+                .get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN), mFeatureFlags, looper,
                 new DataStallRecoveryManagerCallback(this::post) {
                     @Override
                     public void onDataStallReestablishInternet() {
@@ -1045,16 +1052,18 @@
                     }
                 }, this::post);
 
-        // Register for call ended event for voice/data concurrent not supported case. It is
-        // intended to only listen for events from the same phone as most of the telephony modules
-        // are designed as per-SIM basis. For DSDS call ended on non-DDS sub, the frameworks relies
-        // on service state on DDS sub change from out-of-service to in-service to trigger data
-        // retry.
-        mPhone.getCallTracker().registerForVoiceCallEnded(this, EVENT_VOICE_CALL_ENDED, null);
-        // Check null for devices not supporting FEATURE_TELEPHONY_IMS.
-        if (mPhone.getImsPhone() != null) {
-            mPhone.getImsPhone().getCallTracker().registerForVoiceCallEnded(
-                    this, EVENT_VOICE_CALL_ENDED, null);
+        if (hasCalling()) {
+            // Register for call ended event for voice/data concurrent not supported case. It is
+            // intended to only listen for events from the same phone as most of the telephony
+            // modules are designed as per-SIM basis. For DSDS call ended on non-DDS sub, the
+            // frameworks relies on service state on DDS sub change from out-of-service to
+            // in-service to trigger data retry.
+            mPhone.getCallTracker().registerForVoiceCallEnded(this, EVENT_VOICE_CALL_ENDED, null);
+            // Check null for devices not supporting FEATURE_TELEPHONY_IMS.
+            if (mPhone.getImsPhone() != null) {
+                mPhone.getImsPhone().getCallTracker().registerForVoiceCallEnded(
+                        this, EVENT_VOICE_CALL_ENDED, null);
+            }
         }
         mPhone.mCi.registerForSlicingConfigChanged(this, EVENT_SLICE_CONFIG_CHANGED, null);
         mPhone.mCi.registerForSrvccStateChanged(this, EVENT_SRVCC_STATE_CHANGED, null);
@@ -1569,7 +1578,7 @@
         }
 
         // Check CS call state and see if concurrent voice/data is allowed.
-        if (mPhone.getCallTracker().getState() != PhoneConstants.State.IDLE
+        if (hasCalling() && mPhone.getCallTracker().getState() != PhoneConstants.State.IDLE
                 && !mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
             evaluation.addDataDisallowedReason(
                     DataDisallowedReason.CONCURRENT_VOICE_DATA_NOT_ALLOWED);
diff --git a/src/java/com/android/internal/telephony/data/DataSettingsManager.java b/src/java/com/android/internal/telephony/data/DataSettingsManager.java
index e54f6d3..51e5b7d 100644
--- a/src/java/com/android/internal/telephony/data/DataSettingsManager.java
+++ b/src/java/com/android/internal/telephony/data/DataSettingsManager.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.content.ContentResolver;
 import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -47,6 +48,7 @@
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.SettingsObserver;
 import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.DeviceTelephonyPropertiesStats;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
@@ -57,6 +59,7 @@
 import java.io.PrintWriter;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
@@ -88,6 +91,7 @@
     private static final int EVENT_INITIALIZE = 11;
 
     private final Phone mPhone;
+    private final @NonNull FeatureFlags mFeatureFlags;
     private final ContentResolver mResolver;
     private final SettingsObserver mSettingsObserver;
     private final String mLogTag;
@@ -178,10 +182,12 @@
      * @param callback Data settings manager callback.
      */
     public DataSettingsManager(@NonNull Phone phone,
-            @NonNull DataNetworkController dataNetworkController, @NonNull Looper looper,
+            @NonNull DataNetworkController dataNetworkController,
+            @NonNull FeatureFlags featureFlags, @NonNull Looper looper,
             @NonNull DataSettingsManagerCallback callback) {
         super(looper);
         mPhone = phone;
+        mFeatureFlags = Objects.requireNonNull(featureFlags);
         mLogTag = "DSMGR-" + mPhone.getPhoneId();
         log("DataSettingsManager created.");
         mSubId = mPhone.getSubId();
@@ -264,6 +270,12 @@
         }
     }
 
+    private boolean hasCalling() {
+        if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true;
+        return mPhone.getContext().getPackageManager().hasSystemFeature(
+            PackageManager.FEATURE_TELEPHONY_CALLING);
+    }
+
     /**
      * Called when needed to register for all events that data network controller is interested.
      */
@@ -281,9 +293,12 @@
         mSettingsObserver.observe(
                 Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED),
                 EVENT_PROVISIONING_DATA_ENABLED_CHANGED);
-        mPhone.getCallTracker().registerForVoiceCallStarted(this, EVENT_CALL_STATE_CHANGED, null);
-        mPhone.getCallTracker().registerForVoiceCallEnded(this, EVENT_CALL_STATE_CHANGED, null);
-        if (mPhone.getImsPhone() != null) {
+        if (hasCalling()) {
+            mPhone.getCallTracker().registerForVoiceCallStarted(this, EVENT_CALL_STATE_CHANGED,
+                    null);
+            mPhone.getCallTracker().registerForVoiceCallEnded(this, EVENT_CALL_STATE_CHANGED, null);
+        }
+        if (hasCalling() && mPhone.getImsPhone() != null) {
             mPhone.getImsPhone().getCallTracker().registerForVoiceCallStarted(
                     this, EVENT_CALL_STATE_CHANGED, null);
             mPhone.getImsPhone().getCallTracker().registerForVoiceCallEnded(
diff --git a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
index 893509c..ee8890a 100644
--- a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
+++ b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
@@ -48,6 +48,7 @@
 import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback;
 import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.DataStallRecoveryStats;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.telephony.Rlog;
@@ -153,6 +154,7 @@
     private final @NonNull Phone mPhone;
     private final @NonNull String mLogTag;
     private final @NonNull LocalLog mLocalLog = new LocalLog(128);
+    private final @NonNull FeatureFlags mFeatureFlags;
 
     /** Data network controller */
     private final @NonNull DataNetworkController mDataNetworkController;
@@ -196,7 +198,10 @@
     private boolean mIsInternetNetworkConnected;
     /** The durations for current recovery action */
     private @ElapsedRealtimeLong long mTimeElapsedOfCurrentAction;
-
+    /** Tracks the total number of validation duration a data stall */
+    private int mValidationCount;
+    /** Tracks the number of validation for current action during a data stall */
+    private int mActionValidationCount;
     /** The array for the timers between recovery actions. */
     private @NonNull long[] mDataStallRecoveryDelayMillisArray;
     /** The boolean array for the flags. They are used to skip the recovery actions if needed. */
@@ -253,6 +258,7 @@
      * @param phone The phone instance.
      * @param dataNetworkController Data network controller
      * @param dataServiceManager The WWAN data service manager.
+     * @param featureFlags The feature flag.
      * @param looper The looper to be used by the handler. Currently the handler thread is the phone
      *     process's main thread.
      * @param callback Callback to notify data network controller for data stall events.
@@ -261,6 +267,7 @@
             @NonNull Phone phone,
             @NonNull DataNetworkController dataNetworkController,
             @NonNull DataServiceManager dataServiceManager,
+            @NonNull FeatureFlags featureFlags,
             @NonNull Looper looper,
             @NonNull DataStallRecoveryManagerCallback callback) {
         super(looper);
@@ -269,6 +276,7 @@
         log("DataStallRecoveryManager created.");
         mDataNetworkController = dataNetworkController;
         mWwanDataServiceManager = dataServiceManager;
+        mFeatureFlags = featureFlags;
         mDataConfigManager = mDataNetworkController.getDataConfigManager();
         mDataNetworkController
                 .getDataSettingsManager()
@@ -288,7 +296,7 @@
 
         registerAllEvents();
 
-        mStats = new DataStallRecoveryStats(mPhone, dataNetworkController);
+        mStats = new DataStallRecoveryStats(mPhone, mFeatureFlags, dataNetworkController);
     }
 
     /** Register for all events that data stall monitor is interested. */
@@ -546,6 +554,8 @@
         mTimeLastRecoveryStartMs = 0;
         mLastAction = RECOVERY_ACTION_GET_DATA_CALL_LIST;
         mRecoveryAction = RECOVERY_ACTION_GET_DATA_CALL_LIST;
+        mValidationCount = 0;
+        mActionValidationCount = 0;
     }
 
     /**
@@ -556,8 +566,16 @@
     private void onInternetValidationStatusChanged(@ValidationStatus int status) {
         logl("onInternetValidationStatusChanged: " + DataUtils.validationStatusToString(status));
         final boolean isValid = status == NetworkAgent.VALIDATION_STATUS_VALID;
+        if (mFeatureFlags.dsrsDiagnosticsEnabled()) {
+            mValidationCount += 1;
+            mActionValidationCount += 1;
+        }
         setNetworkValidationState(isValid);
         if (isValid) {
+            if (mFeatureFlags.dsrsDiagnosticsEnabled()) {
+                // Broadcast intent that data stall recovered.
+                broadcastDataStallDetected(getRecoveryAction());
+            }
             reset();
         } else if (isRecoveryNeeded(true)) {
             // Set the network as invalid, because recovery is needed
@@ -596,6 +614,10 @@
      */
     @VisibleForTesting
     public void setRecoveryAction(@RecoveryAction int action) {
+        // Reset the validation count for action change
+        if (mFeatureFlags.dsrsDiagnosticsEnabled() && mRecoveryAction != action) {
+            mActionValidationCount = 0;
+        }
         mRecoveryAction = action;
 
         // Check if the mobile data enabled is TRUE, it means that the mobile data setting changed
@@ -674,13 +696,16 @@
         final boolean isRecovered = !mDataStalled;
         final int duration = (int) (SystemClock.elapsedRealtime() - mDataStallStartMs);
         final @RecoveredReason int reason = getRecoveredReason(mIsValidNetwork);
-        final boolean isFirstValidationOfAction = false;
         final int durationOfAction = (int) getDurationOfCurrentRecoveryMs();
+        if (mFeatureFlags.dsrsDiagnosticsEnabled()) {
+            log("mValidationCount=" + mValidationCount
+                    + ", mActionValidationCount=" + mActionValidationCount);
+        }
 
         // Get the bundled DSRS stats.
         Bundle bundle = mStats.getDataStallRecoveryMetricsData(
-                recoveryAction, isRecovered, duration, reason, isFirstValidationOfAction,
-                durationOfAction);
+                recoveryAction, isRecovered, duration, reason, mValidationCount,
+                mActionValidationCount, durationOfAction);
 
         // Put the bundled stats extras on the intent.
         intent.putExtra("EXTRA_DSRS_STATS_BUNDLE", bundle);
diff --git a/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java
index cb4ddab..66b977d 100644
--- a/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java
@@ -37,10 +37,12 @@
 import android.telephony.DomainSelectionService;
 import android.telephony.EmergencyRegistrationResult;
 import android.telephony.NetworkRegistrationInfo;
+import android.telephony.PreciseDisconnectCause;
 import android.telephony.data.ApnSetting;
 import android.telephony.ims.ImsReasonInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.CallFailCause;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.data.AccessNetworksManager;
 import com.android.internal.telephony.data.AccessNetworksManager.QualifiedNetworks;
@@ -215,6 +217,19 @@
             @NonNull String callId, @NonNull String number, boolean isTest,
             int callFailCause, @Nullable ImsReasonInfo imsReasonInfo,
             @Nullable EmergencyRegistrationResult emergencyRegResult) {
+
+        int preciseDisconnectCause = callFailCause;
+        switch (callFailCause) {
+            case CallFailCause.IMS_EMERGENCY_TEMP_FAILURE:
+                preciseDisconnectCause = PreciseDisconnectCause.EMERGENCY_TEMP_FAILURE;
+                break;
+            case CallFailCause.IMS_EMERGENCY_PERM_FAILURE:
+                preciseDisconnectCause = PreciseDisconnectCause.EMERGENCY_PERM_FAILURE;
+                break;
+            default:
+                break;
+        }
+
         DomainSelectionService.SelectionAttributes.Builder builder =
                 new DomainSelectionService.SelectionAttributes.Builder(
                         slotId, subId, SELECTOR_TYPE_CALLING)
@@ -223,7 +238,7 @@
                 .setExitedFromAirplaneMode(exited)
                 .setCallId(callId)
                 .setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null))
-                .setCsDisconnectCause(callFailCause);
+                .setCsDisconnectCause(preciseDisconnectCause);
 
         if (imsReasonInfo != null) builder.setPsDisconnectCause(imsReasonInfo);
         if (emergencyRegResult != null) builder.setEmergencyRegistrationResult(emergencyRegResult);
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
index e2418c5..02dd613 100644
--- a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
@@ -16,10 +16,12 @@
 
 package com.android.internal.telephony.emergency;
 
+import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.os.AsyncResult;
 import android.os.Environment;
@@ -48,6 +50,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.EmergencyNumberStats;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.nano.PersistAtomsProto;
@@ -102,6 +105,7 @@
 
     private final CommandsInterface mCi;
     private final Phone mPhone;
+    private final @NonNull FeatureFlags mFeatureFlags;
     private int mPhoneId;
     private String mCountryIso;
     private String mLastKnownEmergencyCountryIso = "";
@@ -173,10 +177,20 @@
         }
     };
 
-    public EmergencyNumberTracker(Phone phone, CommandsInterface ci) {
+    public EmergencyNumberTracker(Phone phone, CommandsInterface ci,
+            @NonNull FeatureFlags featureFlags) {
+        Context ctx = phone.getContext();
+
         mPhone = phone;
         mCi = ci;
-        mResources = mPhone.getContext().getResources();
+        mFeatureFlags = featureFlags;
+        mResources = ctx.getResources();
+
+        if (mFeatureFlags.minimalTelephonyCdmCheck()
+                && !ctx.getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_TELEPHONY_CALLING)) {
+            throw new UnsupportedOperationException("EmergencyNumberTracker requires calling");
+        }
 
         if (mPhone != null) {
             mPhoneId = phone.getPhoneId();
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
index a35cccf..c4d5355 100644
--- a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
@@ -46,12 +46,9 @@
 import android.telephony.DisconnectCause;
 import android.telephony.EmergencyRegistrationResult;
 import android.telephony.NetworkRegistrationInfo;
-import android.telephony.PreciseDataConnectionState;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
-import android.telephony.data.ApnSetting;
 import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -71,7 +68,6 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
@@ -92,7 +88,6 @@
     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;
 
@@ -132,9 +127,6 @@
     private final Runnable mExitEcmRunnable = this::exitEmergencyCallbackMode;
     // Tracks emergency calls by callId that have reached {@link Call.State#ACTIVE}.
     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 connection to handle a second emergency call
     private android.telecom.Connection mOngoingConnection;
@@ -152,6 +144,9 @@
     private Phone mSmsPhone;
     private CompletableFuture<Integer> mSmsEmergencyModeFuture;
     private boolean mIsTestEmergencyNumberForSms;
+    // For tracking the emergency SMS callback mode.
+    private boolean mIsInScbm;
+    private boolean mIsEmergencySmsStartedDuringScbm;
 
     private CompletableFuture<Boolean> mEmergencyTransportChangedFuture;
 
@@ -182,29 +177,6 @@
         }
     };
 
-    /**
-     * TelephonyCallback used to monitor whether ePDN on cellular network is disconnected or not.
-     */
-    private final class PreciseDataConnectionStateListener extends TelephonyCallback implements
-            TelephonyCallback.PreciseDataConnectionStateListener {
-        @Override
-        public void onPreciseDataConnectionStateChanged(
-                @NonNull PreciseDataConnectionState dataConnectionState) {
-            ApnSetting apnSetting = dataConnectionState.getApnSetting();
-            if ((apnSetting == null)
-                    || ((apnSetting.getApnTypeBitmask() | ApnSetting.TYPE_EMERGENCY) == 0)
-                    || (dataConnectionState.getTransportType()
-                            != AccessNetworkConstants.TRANSPORT_TYPE_WWAN)) {
-                return;
-            }
-            int state = dataConnectionState.getState();
-            Rlog.d(TAG, "onPreciseDataConnectionStateChanged ePDN state=" + state);
-            if (state == TelephonyManager.DATA_DISCONNECTED) exitEmergencyModeIfDelayed();
-        }
-    }
-
-    private PreciseDataConnectionStateListener mDataConnectionStateListener;
-
     /** PhoneFactory Dependencies for testing. */
     @VisibleForTesting
     public interface PhoneFactoryProxy {
@@ -228,8 +200,6 @@
     @VisibleForTesting
     public interface TelephonyManagerProxy {
         int getPhoneCount();
-        void registerTelephonyCallback(int subId, Executor executor, TelephonyCallback callback);
-        void unregisterTelephonyCallback(TelephonyCallback callback);
     }
 
     private final TelephonyManagerProxy mTelephonyManagerProxy;
@@ -245,18 +215,6 @@
         public int getPhoneCount() {
             return mTelephonyManager.getActiveModemCount();
         }
-
-        @Override
-        public void registerTelephonyCallback(int subId,
-                Executor executor, TelephonyCallback callback) {
-            TelephonyManager tm = mTelephonyManager.createForSubscriptionId(subId);
-            tm.registerTelephonyCallback(executor, callback);
-        }
-
-        @Override
-        public void unregisterTelephonyCallback(TelephonyCallback callback) {
-            mTelephonyManager.unregisterTelephonyCallback(callback);
-        }
     }
 
     /**
@@ -268,15 +226,13 @@
     }
 
     @VisibleForTesting
-    public static final int MSG_SET_EMERGENCY_MODE = 1;
+    public static final int MSG_SET_EMERGENCY_MODE_DONE = 1;
     @VisibleForTesting
-    public static final int MSG_EXIT_EMERGENCY_MODE = 2;
+    public static final int MSG_EXIT_EMERGENCY_MODE_DONE = 2;
     @VisibleForTesting
-    public static final int MSG_SET_EMERGENCY_MODE_DONE = 3;
-    @VisibleForTesting
-    public static final int MSG_EXIT_EMERGENCY_MODE_DONE = 4;
-    @VisibleForTesting
-    public static final int MSG_SET_EMERGENCY_CALLBACK_MODE_DONE = 5;
+    public static final int MSG_SET_EMERGENCY_CALLBACK_MODE_DONE = 3;
+    /** A message which is used to automatically exit from SCBM after a period of time. */
+    private static final int MSG_EXIT_SCBM = 4;
 
     private class MyHandler extends Handler {
 
@@ -312,38 +268,46 @@
                         // Case 1) When the emergency call is setting the emergency mode and
                         // the emergency SMS is being sent, completes the SMS future also.
                         // Case 2) When the emergency SMS is setting the emergency mode and
-                        // the emergency call is beint started, the SMS request is cancelled and
+                        // the emergency call is being started, the SMS request is cancelled and
                         // the call request will be handled.
                         if (mSmsPhone != null) {
                             completeEmergencyMode(EMERGENCY_TYPE_SMS);
                         }
                     } else if (emergencyType == EMERGENCY_TYPE_SMS) {
                         if (mPhone != null && mSmsPhone != null) {
-                            // Clear call phone temporarily to exit the emergency mode
-                            // if the emergency call is started.
                             if (mIsEmergencyCallStartedDuringEmergencySms) {
-                                Phone phone = mPhone;
-                                mPhone = null;
-                                exitEmergencyMode(mSmsPhone, emergencyType, false);
-                                // Restore call phone for further use.
-                                mPhone = phone;
-
-                                if (!isSamePhone(mPhone, mSmsPhone)) {
-                                    completeEmergencyMode(emergencyType,
-                                            DisconnectCause.OUTGOING_EMERGENCY_CALL_PLACED);
+                                if (!isSamePhone(mPhone, mSmsPhone) || !isInScbm()) {
+                                    // Clear call phone temporarily to exit the emergency mode
+                                    // if the emergency call is started.
+                                    Phone phone = mPhone;
+                                    mPhone = null;
+                                    exitEmergencyMode(mSmsPhone, emergencyType);
+                                    // Restore call phone for further use.
+                                    mPhone = phone;
+                                    if (!isSamePhone(mPhone, mSmsPhone)) {
+                                        completeEmergencyMode(emergencyType,
+                                                DisconnectCause.OUTGOING_EMERGENCY_CALL_PLACED);
+                                        exitEmergencySmsCallbackMode();
+                                    }
+                                } else {
+                                    completeEmergencyMode(emergencyType);
+                                    mIsEmergencyCallStartedDuringEmergencySms = false;
+                                    exitEmergencySmsCallbackMode();
+                                    turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL,
+                                            mIsTestEmergencyNumber);
                                 }
                             } else {
                                 completeEmergencyMode(emergencyType);
                             }
-                            break;
                         } else {
                             completeEmergencyMode(emergencyType);
-                        }
 
-                        if (mIsEmergencyCallStartedDuringEmergencySms) {
-                            mIsEmergencyCallStartedDuringEmergencySms = false;
-                            turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL,
-                                    mIsTestEmergencyNumber);
+                            if (mIsEmergencyCallStartedDuringEmergencySms) {
+                                mIsEmergencyCallStartedDuringEmergencySms = false;
+                                exitEmergencySmsCallbackMode();
+                                turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL,
+                                        mIsTestEmergencyNumber);
+                            }
                         }
                     }
                     break;
@@ -366,6 +330,10 @@
                             mIsEmergencyCallStartedDuringEmergencySms = false;
                             turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL,
                                     mIsTestEmergencyNumber);
+                        } else if (mIsEmergencySmsStartedDuringScbm) {
+                            mIsEmergencySmsStartedDuringScbm = false;
+                            setEmergencyMode(mSmsPhone, emergencyType,
+                                    MODE_EMERGENCY_WWAN, MSG_SET_EMERGENCY_MODE_DONE);
                         }
                     }
                     break;
@@ -378,30 +346,35 @@
                     setEmergencyModeInProgress(false);
                     // When the emergency callback mode is in progress and the emergency SMS is
                     // started, it needs to be completed here for the emergency SMS.
-                    if (mSmsPhone != null) {
-                        completeEmergencyMode(EMERGENCY_TYPE_SMS);
+                    if (emergencyType == EMERGENCY_TYPE_CALL) {
+                        if (mSmsPhone != null) {
+                            completeEmergencyMode(EMERGENCY_TYPE_SMS);
+                        }
+                    } else if (emergencyType == EMERGENCY_TYPE_SMS) {
+                        // When the emergency SMS callback mode is in progress on other phone and
+                        // the emergency call was started, needs to exit the emergency mode first.
+                        if (mIsEmergencyCallStartedDuringEmergencySms) {
+                            final Phone smsPhone = mSmsPhone;
+                            exitEmergencySmsCallbackMode();
+
+                            if (mPhone != null && smsPhone != null
+                                    && !isSamePhone(mPhone, smsPhone)) {
+                                Phone phone = mPhone;
+                                mPhone = null;
+                                exitEmergencyMode(smsPhone, emergencyType);
+                                // Restore call phone for further use.
+                                mPhone = phone;
+                            } else {
+                                mIsEmergencyCallStartedDuringEmergencySms = false;
+                                turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL,
+                                        mIsTestEmergencyNumber);
+                            }
+                        }
                     }
                     break;
                 }
-                case MSG_EXIT_EMERGENCY_MODE: {
-                    Rlog.v(TAG, "MSG_EXIT_EMERGENCY_MODE");
-                    exitEmergencyModeIfDelayed();
-                    break;
-                }
-                case MSG_SET_EMERGENCY_MODE: {
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    Integer emergencyType = (Integer) ar.userObj;
-                    Rlog.v(TAG, "MSG_SET_EMERGENCY_MODE for "
-                            + emergencyTypeToString(emergencyType) + ", " + mEmergencyMode);
-                    // Should be reached here only when starting a new emergency service
-                    // while exiting emergency callback mode on the other slot.
-                    if (mEmergencyMode != MODE_EMERGENCY_WWAN) return;
-                    final Phone phone = (mPhone != null) ? mPhone : mSmsPhone;
-                    if (phone != null) {
-                        mWasEmergencyModeSetOnModem = true;
-                        phone.setEmergencyMode(MODE_EMERGENCY_WWAN,
-                                mHandler.obtainMessage(MSG_SET_EMERGENCY_MODE_DONE, emergencyType));
-                    }
+                case MSG_EXIT_SCBM: {
+                    exitEmergencySmsCallbackModeAndEmergencyMode();
                     break;
                 }
                 default:
@@ -522,19 +495,26 @@
             // 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())) {
+                exitEmergencySmsCallbackMode();
                 mOngoingConnection = c;
                 mIsTestEmergencyNumber = isTestEmergencyNumber;
-                // Ensure that domain selector requests scan.
-                mLastEmergencyRegistrationResult = new EmergencyRegistrationResult(
-                        AccessNetworkConstants.AccessNetworkType.UNKNOWN,
-                        NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN,
-                        NetworkRegistrationInfo.DOMAIN_UNKNOWN, false, false, 0, 0, "", "", "");
                 if (isInEcm()) {
                     // Remove pending exit ECM runnable.
                     mHandler.removeCallbacks(mExitEcmRunnable);
                     releaseWakeLock();
                     ((GsmCdmaPhone) mPhone).notifyEcbmTimerReset(Boolean.TRUE);
+
+                    mOngoingCallProperties = 0;
+                    mCallEmergencyModeFuture = new CompletableFuture<>();
+                    setEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN,
+                            MSG_SET_EMERGENCY_MODE_DONE);
+                    return mCallEmergencyModeFuture;
                 }
+                // Ensure that domain selector requests scan.
+                mLastEmergencyRegistrationResult = new EmergencyRegistrationResult(
+                        AccessNetworkConstants.AccessNetworkType.UNKNOWN,
+                        NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN,
+                        NetworkRegistrationInfo.DOMAIN_UNKNOWN, false, false, 0, 0, "", "", "");
                 return CompletableFuture.completedFuture(DisconnectCause.NOT_DISCONNECTED);
             }
 
@@ -553,13 +533,25 @@
             // exit the emergency mode when receiving the result of setting the emergency mode and
             // the emergency mode for this call will be restarted after the exit complete.
             if (isInEmergencyMode() && !isEmergencyModeInProgress()) {
-                exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS, false);
+                if (!isSamePhone(mSmsPhone, phone)) {
+                    exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS);
+                } else {
+                    // If the device is already in the emergency mode on the same phone,
+                    // the general emergency call procedure can be immediately performed.
+                    // And, if the emergency PDN is already connected, then we need to keep
+                    // this PDN active while initating the emergency call.
+                    mIsEmergencyCallStartedDuringEmergencySms = false;
+                }
+
+                exitEmergencySmsCallbackMode();
             }
 
-            mPhone = phone;
-            mOngoingConnection = c;
-            mIsTestEmergencyNumber = isTestEmergencyNumber;
-            return mCallEmergencyModeFuture;
+            if (mIsEmergencyCallStartedDuringEmergencySms) {
+                mPhone = phone;
+                mOngoingConnection = c;
+                mIsTestEmergencyNumber = isTestEmergencyNumber;
+                return mCallEmergencyModeFuture;
+            }
         }
 
         mPhone = phone;
@@ -587,7 +579,7 @@
         }
 
         if (wasActive && mActiveEmergencyCalls.isEmpty()
-                && isEmergencyCallbackModeSupported()) {
+                && isEmergencyCallbackModeSupported(mPhone)) {
             enterEmergencyCallbackMode();
 
             if (mOngoingConnection == null) {
@@ -604,7 +596,12 @@
                     enterEmergencyCallbackMode();
                 }
             } else {
-                exitEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, false);
+                if (isInScbm()) {
+                    setIsInEmergencyCall(false);
+                    setEmergencyCallbackMode(mSmsPhone, EMERGENCY_TYPE_SMS);
+                } else {
+                    exitEmergencyMode(mPhone, EMERGENCY_TYPE_CALL);
+                }
                 clearEmergencyCallInfo();
             }
         }
@@ -635,8 +632,20 @@
             // 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);
+            if (mEmergencyMode != MODE_EMERGENCY_WWAN) {
+                setEmergencyMode(phone, emergencyType, MODE_EMERGENCY_WWAN,
+                        MSG_SET_EMERGENCY_MODE_DONE);
+            } else {
+                // Ensure that domain selector requests the network scan.
+                mLastEmergencyRegistrationResult = new EmergencyRegistrationResult(
+                        AccessNetworkConstants.AccessNetworkType.UNKNOWN,
+                        NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN,
+                        NetworkRegistrationInfo.DOMAIN_UNKNOWN, false, false, 0, 0, "", "", "");
+                if (emergencyType == EMERGENCY_TYPE_CALL) {
+                    setIsInEmergencyCall(true);
+                }
+                completeEmergencyMode(emergencyType);
+            }
         });
     }
 
@@ -673,26 +682,33 @@
             return;
         }
 
-        synchronized (mLock) {
-            unregisterForDataConnectionStateChanges();
-            if (mPhoneToExit != null) {
-                if (emergencyType != EMERGENCY_TYPE_CALL) {
-                    setIsInEmergencyCall(false);
-                }
-                mOnEcmExitCompleteRunnable = null;
-                if (mPhoneToExit != phone) {
-                    // Exit emergency mode on the other phone first,
-                    // then set emergency mode on the given phone.
-                    mPhoneToExit.exitEmergencyMode(
-                            mHandler.obtainMessage(MSG_SET_EMERGENCY_MODE,
-                            Integer.valueOf(emergencyType)));
-                    mPhoneToExit = null;
-                    return;
-                }
-                mPhoneToExit = null;
+        mWasEmergencyModeSetOnModem = true;
+        phone.setEmergencyMode(mode, m);
+    }
+
+    /**
+     * Sets the emergency callback mode on modem.
+     *
+     * @param phone the {@code Phone} to set the emergency mode on modem.
+     * @param emergencyType the emergency type to identify an emergency call or SMS.
+     */
+    private void setEmergencyCallbackMode(Phone phone, @EmergencyType int emergencyType) {
+        boolean needToSetCallbackMode = false;
+
+        if (emergencyType == EMERGENCY_TYPE_CALL) {
+            needToSetCallbackMode = true;
+        } else if (emergencyType == EMERGENCY_TYPE_SMS) {
+            // Ensure that no emergency call is in progress.
+            if (mActiveEmergencyCalls.isEmpty() && mOngoingConnection == null
+                    && mOngoingEmergencySmsIds.isEmpty()) {
+                needToSetCallbackMode = true;
             }
-            mWasEmergencyModeSetOnModem = true;
-            phone.setEmergencyMode(mode, m);
+        }
+
+        if (needToSetCallbackMode) {
+            // Set emergency mode on modem.
+            setEmergencyMode(phone, emergencyType, MODE_EMERGENCY_CALLBACK,
+                    MSG_SET_EMERGENCY_CALLBACK_MODE_DONE);
         }
     }
 
@@ -766,10 +782,8 @@
      *
      * @param phone the {@code Phone} to exit the emergency mode.
      * @param emergencyType the emergency type to identify an emergency call or SMS.
-     * @param waitForPdnDisconnect indicates whether it shall wait for the disconnection of ePDN.
      */
-    private void exitEmergencyMode(Phone phone, @EmergencyType int emergencyType,
-            boolean waitForPdnDisconnect) {
+    private void exitEmergencyMode(Phone phone, @EmergencyType int emergencyType) {
         Rlog.i(TAG, "exitEmergencyMode for " + emergencyTypeToString(emergencyType));
 
         if (emergencyType == EMERGENCY_TYPE_CALL) {
@@ -805,23 +819,8 @@
             return;
         }
 
-        synchronized (mLock) {
-            mWasEmergencyModeSetOnModem = false;
-            if (waitForPdnDisconnect) {
-                registerForDataConnectionStateChanges(phone);
-                mPhoneToExit = phone;
-                if (mPdnDisconnectionTimeoutMs > 0) {
-                    // To avoid waiting for the disconnection indefinitely.
-                    mHandler.sendEmptyMessageDelayed(MSG_EXIT_EMERGENCY_MODE,
-                            mPdnDisconnectionTimeoutMs);
-                }
-                return;
-            } else {
-                unregisterForDataConnectionStateChanges();
-                mPhoneToExit = null;
-            }
-            phone.exitEmergencyMode(m);
-        }
+        mWasEmergencyModeSetOnModem = false;
+        phone.exitEmergencyMode(m);
     }
 
     /** Returns last {@link EmergencyRegistrationResult} as set by {@code setEmergencyMode()}. */
@@ -985,12 +984,8 @@
      * Handles the radio power off request.
      */
     public void onCellularRadioPowerOffRequested() {
-        synchronized (mLock) {
-            if (isInEcm()) {
-                exitEmergencyCallbackMode(null);
-            }
-            exitEmergencyModeIfDelayed();
-        }
+        exitEmergencySmsCallbackModeAndEmergencyMode();
+        exitEmergencyCallbackMode();
     }
 
     private static boolean isVoWiFi(int properties) {
@@ -1000,14 +995,16 @@
 
     /**
      * Returns {@code true} if device and carrier support emergency callback mode.
+     *
+     * @param phone The {@link Phone} instance to be checked.
      */
     @VisibleForTesting
-    public boolean isEmergencyCallbackModeSupported() {
-        int subId = mPhone.getSubId();
+    public boolean isEmergencyCallbackModeSupported(Phone phone) {
+        int subId = phone.getSubId();
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
             // If there is no SIM, refer to the saved last carrier configuration with valid
             // subscription.
-            int phoneId = mPhone.getPhoneId();
+            int phoneId = phone.getPhoneId();
             Boolean savedConfig = mNoSimEcbmSupported.get(Integer.valueOf(phoneId));
             if (savedConfig == null) {
                 // Exceptional case such as with poor boot performance.
@@ -1054,9 +1051,7 @@
             ((GsmCdmaPhone) mPhone).notifyEcbmTimerReset(Boolean.FALSE);
         }
 
-        // Set emergency mode on modem.
-        setEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, MODE_EMERGENCY_CALLBACK,
-                MSG_SET_EMERGENCY_CALLBACK_MODE_DONE);
+        setEmergencyCallbackMode(mPhone, EMERGENCY_TYPE_CALL);
 
         // Post this runnable so we will automatically exit if no one invokes
         // exitEmergencyCallbackMode() directly.
@@ -1091,9 +1086,7 @@
             gsmCdmaPhone.notifyEmergencyCallRegistrants(false);
 
             // Exit emergency mode on modem.
-            // b/299866883: Wait for the disconnection of ePDN before calling exitEmergencyMode.
-            exitEmergencyMode(gsmCdmaPhone, EMERGENCY_TYPE_CALL,
-                    mEmergencyCallDomain == NetworkRegistrationInfo.DOMAIN_PS);
+            exitEmergencyMode(gsmCdmaPhone, EMERGENCY_TYPE_CALL);
         }
 
         mEmergencyCallDomain = NetworkRegistrationInfo.DOMAIN_UNKNOWN;
@@ -1182,7 +1175,8 @@
      */
     public CompletableFuture<Integer> startEmergencySms(@NonNull Phone phone, @NonNull String smsId,
             boolean isTestEmergencyNumber) {
-        Rlog.i(TAG, "startEmergencySms: phoneId=" + phone.getPhoneId() + ", smsId=" + smsId);
+        Rlog.i(TAG, "startEmergencySms: phoneId=" + phone.getPhoneId() + ", smsId=" + smsId
+                + ", scbm=" + isInScbm());
 
         // When an emergency call is in progress, it checks whether an emergency call is already in
         // progress on the different phone.
@@ -1191,17 +1185,29 @@
             return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED);
         }
 
-        // When an emergency SMS is in progress, it checks whether an emergency SMS is already in
-        // progress on the different phone.
+        boolean exitScbmInOtherPhone = false;
+        boolean smsStartedInScbm = isInScbm();
+
+        // When an emergency SMS is in progress, it checks whether an emergency SMS is already
+        // in progress on the different phone.
         if (mSmsPhone != null && !isSamePhone(mSmsPhone, phone)) {
-            Rlog.e(TAG, "Emergency SMS is in progress on the other slot.");
-            return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED);
+            if (smsStartedInScbm) {
+                // When other phone is in the emergency SMS callback mode, we need to stop the
+                // emergency SMS callback mode first.
+                exitScbmInOtherPhone = true;
+                mIsEmergencySmsStartedDuringScbm = true;
+                exitEmergencySmsCallbackModeAndEmergencyMode();
+            } else {
+                Rlog.e(TAG, "Emergency SMS is in progress on the other slot.");
+                return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED);
+            }
         }
 
-        // When the previous emergency SMS is not completed yet,
+        // When the previous emergency SMS is not completed yet and the device is not in SCBM,
         // this new request will not be allowed.
-        if (mSmsPhone != null && isInEmergencyMode() && isEmergencyModeInProgress()) {
-            Rlog.e(TAG, "Existing emergency SMS is in progress.");
+        if (mSmsPhone != null && isInEmergencyMode() && isEmergencyModeInProgress()
+                && !smsStartedInScbm) {
+            Rlog.e(TAG, "Existing emergency SMS is in progress and not in SCBM.");
             return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED);
         }
 
@@ -1209,17 +1215,31 @@
         mIsTestEmergencyNumberForSms = isTestEmergencyNumber;
         mOngoingEmergencySmsIds.add(smsId);
 
-        // When the emergency mode is already set by the previous emergency call or SMS,
-        // completes the future immediately.
-        if (isInEmergencyMode() && !isEmergencyModeInProgress()) {
-            return CompletableFuture.completedFuture(DisconnectCause.NOT_DISCONNECTED);
+        if (smsStartedInScbm) {
+            // When the device is in SCBM and emergency SMS is being sent,
+            // completes the future immediately.
+            if (!exitScbmInOtherPhone) {
+                // The emergency SMS is allowed and returns the success result.
+                return CompletableFuture.completedFuture(DisconnectCause.NOT_DISCONNECTED);
+            }
+
+            mSmsEmergencyModeFuture = new CompletableFuture<>();
+        } else {
+            // When the emergency mode is already set by the previous emergency call or SMS,
+            // completes the future immediately.
+            if (isInEmergencyMode() && !isEmergencyModeInProgress()) {
+                // The emergency SMS is allowed and returns the success result.
+                return CompletableFuture.completedFuture(DisconnectCause.NOT_DISCONNECTED);
+            }
+
+            mSmsEmergencyModeFuture = new CompletableFuture<>();
+
+            if (!isInEmergencyMode()) {
+                setEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN,
+                        MSG_SET_EMERGENCY_MODE_DONE);
+            }
         }
 
-        mSmsEmergencyModeFuture = new CompletableFuture<>();
-        if (!isInEmergencyMode()) {
-            setEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN,
-                    MSG_SET_EMERGENCY_MODE_DONE);
-        }
         return mSmsEmergencyModeFuture;
     }
 
@@ -1230,35 +1250,142 @@
      * @param smsId the SMS id on which to end the emergency SMS.
      * @param success the flag specifying whether an emergency SMS is successfully sent or not.
      *                {@code true} if SMS is successfully sent, {@code false} otherwise.
+     * @param domain the domain that MO SMS was sent.
      */
-    public void endSms(@NonNull String smsId, boolean success) {
+    public void endSms(@NonNull String smsId, boolean success,
+            @NetworkRegistrationInfo.Domain int domain) {
         mOngoingEmergencySmsIds.remove(smsId);
 
         // If the outgoing emergency SMSs are empty, we can try to exit the emergency mode.
         if (mOngoingEmergencySmsIds.isEmpty()) {
+            mSmsEmergencyModeFuture = null;
+            mIsEmergencySmsStartedDuringScbm = false;
+
             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() && mOngoingConnection == null) {
-                    setEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, MODE_EMERGENCY_CALLBACK,
-                            MSG_SET_EMERGENCY_CALLBACK_MODE_DONE);
+                    setEmergencyCallbackMode(mPhone, EMERGENCY_TYPE_CALL);
                 }
-            } else {
-                exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS, false);
             }
 
-            clearEmergencySmsInfo();
+            // If SCBM supports, SCBM will be entered here regardless of ECBM state.
+            if (success && domain == NetworkRegistrationInfo.DOMAIN_PS
+                    && (isInScbm() || isEmergencyCallbackModeSupported(mSmsPhone))) {
+                enterEmergencySmsCallbackMode();
+            } else if (isInScbm()) {
+                // Sets the emergency mode to CALLBACK without re-initiating SCBM timer.
+                setEmergencyCallbackMode(mSmsPhone, EMERGENCY_TYPE_SMS);
+            } else {
+                exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS);
+                clearEmergencySmsInfo();
+            }
+        }
+    }
+
+    /**
+     * Called when emergency SMS is received from the network.
+     */
+    public void onEmergencySmsReceived() {
+        if (isInScbm()) {
+            Rlog.d(TAG, "Emergency SMS received, re-initiate SCBM timer");
+            // Reinitiate the SCBM timer when receiving emergency SMS while in SCBM.
+            enterEmergencySmsCallbackMode();
         }
     }
 
     private void clearEmergencySmsInfo() {
         mOngoingEmergencySmsIds.clear();
+        mIsEmergencySmsStartedDuringScbm = false;
         mIsTestEmergencyNumberForSms = false;
         mSmsEmergencyModeFuture = null;
         mSmsPhone = null;
     }
 
     /**
+     * Returns {@code true} if currently in emergency SMS callback mode.
+     */
+    public boolean isInScbm() {
+        return mIsInScbm;
+    }
+
+    /**
+     * Sets the emergency SMS callback mode state.
+     *
+     * @param isInScbm {@code true} if currently in emergency SMS callback mode,
+     *                 {@code false} otherwise.
+     */
+    private void setIsInScbm(boolean isInScbm) {
+        mIsInScbm = isInScbm;
+    }
+
+    /**
+     * Enters the emergency SMS callback mode.
+     */
+    private void enterEmergencySmsCallbackMode() {
+        Rlog.d(TAG, "enter SCBM while " + (isInScbm() ? "in" : "not in") + " SCBM");
+        // Remove pending message if present.
+        mHandler.removeMessages(MSG_EXIT_SCBM);
+
+        if (!isInScbm()) {
+            setIsInScbm(true);
+        }
+
+        setEmergencyCallbackMode(mSmsPhone, EMERGENCY_TYPE_SMS);
+
+        // At the moment, the default SCBM timer value will be used with the same value
+        // that is configured for emergency callback mode.
+        int delayInMillis = Long.valueOf(mEcmExitTimeoutMs).intValue();
+        int subId = mSmsPhone.getSubId();
+        if (SubscriptionManager.isValidSubscriptionId(subId)) {
+            delayInMillis = getConfig(subId,
+                    CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT,
+                    delayInMillis);
+            if (delayInMillis == 0) {
+                delayInMillis = Long.valueOf(mEcmExitTimeoutMs).intValue();
+            }
+        }
+        // Post the message so we will automatically exit if no one invokes
+        // exitEmergencySmsCallbackModeAndEmergencyMode() directly.
+        mHandler.sendEmptyMessageDelayed(MSG_EXIT_SCBM, delayInMillis);
+    }
+
+    /**
+     * Exits emergency SMS callback mode and emergency mode if the device is in SCBM and
+     * the emergency mode is in CALLBACK.
+     */
+    private void exitEmergencySmsCallbackModeAndEmergencyMode() {
+        Rlog.d(TAG, "exit SCBM and emergency mode");
+        final Phone smsPhone = mSmsPhone;
+        boolean wasInScbm = isInScbm();
+        exitEmergencySmsCallbackMode();
+
+        // The emergency mode needs to be checked to ensure that there is no ongoing emergency SMS.
+        if (wasInScbm && mOngoingEmergencySmsIds.isEmpty()) {
+            // Exit emergency mode on modem.
+            exitEmergencyMode(smsPhone, EMERGENCY_TYPE_SMS);
+        }
+    }
+
+    /**
+     * Exits emergency SMS callback mode.
+     */
+    private void exitEmergencySmsCallbackMode() {
+        // Remove pending message if present.
+        mHandler.removeMessages(MSG_EXIT_SCBM);
+
+        if (isInScbm()) {
+            Rlog.i(TAG, "exit SCBM");
+            setIsInScbm(false);
+        }
+
+        if (mOngoingEmergencySmsIds.isEmpty()) {
+            mIsTestEmergencyNumberForSms = false;
+            mSmsPhone = null;
+        }
+    }
+
+    /**
      * Returns {@code true} if any phones from PhoneFactory have radio on.
      */
     private boolean isRadioOn() {
@@ -1596,49 +1723,4 @@
         Rlog.i(TAG, "updateNoSimEcbmSupported preference updated slotIndex=" + slotIndex
                 + ", supported=" + carrierConfig);
     }
-
-    /** For test purpose only */
-    @VisibleForTesting
-    public void setPdnDisconnectionTimeoutMs(int timeout) {
-        mPdnDisconnectionTimeoutMs = timeout;
-    }
-
-    private void exitEmergencyModeIfDelayed() {
-        synchronized (mLock) {
-            if (mPhoneToExit != null) {
-                unregisterForDataConnectionStateChanges();
-                mPhoneToExit.exitEmergencyMode(
-                        mHandler.obtainMessage(MSG_EXIT_EMERGENCY_MODE_DONE,
-                                Integer.valueOf(EMERGENCY_TYPE_CALL)));
-                mPhoneToExit = null;
-            }
-        }
-    }
-
-    /**
-     * Registers for changes to data connection state.
-     */
-    private void registerForDataConnectionStateChanges(Phone phone) {
-        if ((mDataConnectionStateListener != null) || (phone == null)) {
-            return;
-        }
-        Rlog.i(TAG, "registerForDataConnectionStateChanges");
-
-        mDataConnectionStateListener = new PreciseDataConnectionStateListener();
-        mTelephonyManagerProxy.registerTelephonyCallback(phone.getSubId(),
-                mHandler::post, mDataConnectionStateListener);
-    }
-
-    /**
-     * Unregisters for changes to data connection state.
-     */
-    private void unregisterForDataConnectionStateChanges() {
-        if (mDataConnectionStateListener == null) {
-            return;
-        }
-        Rlog.i(TAG, "unregisterForDataConnectionStateChanges");
-
-        mTelephonyManagerProxy.unregisterTelephonyCallback(mDataConnectionStateListener);
-        mDataConnectionStateListener = null;
-    }
 }
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccController.java b/src/java/com/android/internal/telephony/euicc/EuiccController.java
index e2f95da..18b4b14 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java
@@ -1331,8 +1331,13 @@
                     SubscriptionInfo subscriptionInfo =
                               mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(
                                     slot.getPhoneIdFromPortIndex(portIndex));
-                    if (subscriptionInfo == null || subscriptionInfo.isOpportunistic()) {
-                            // If the port is active and empty/opportunistic, return the portIndex.
+                    if (subscriptionInfo == null
+                        || subscriptionInfo.isOpportunistic()
+                        || (mFeatureFlags.esimBootstrapProvisioningFlag()
+                            && subscriptionInfo.getProfileClass()
+                            == SubscriptionManager.PROFILE_CLASS_PROVISIONING)) {
+                            // If the port is active and has empty/opportunistic/provisioning
+                            // profiles then return the portIndex.
                         return portIndex;
                     }
                 }
diff --git a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index dae808a..e5afbeb 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -171,7 +171,7 @@
             if(mPhone.getServiceState().getRilDataRadioTechnology()
                     != ServiceState.RIL_RADIO_TECHNOLOGY_NR) {
                 tracker.onFailed(mContext, getNotInServiceError(ss), NO_ERROR_CODE);
-                notifySmsSentFailedToEmergencyStateTracker(tracker);
+                notifySmsSentFailedToEmergencyStateTracker(tracker, false);
                 return;
             }
         }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index b5a052d..dcb3b20 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -3580,14 +3580,13 @@
                 ImsPhoneConnection conn = findConnection(imsCall);
                 // Since onCallInitiating and onCallProgressing reset mPendingMO,
                 // we can't depend on mPendingMO.
-                if ((reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL
-                        || reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED
-                        || reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED)
-                        && conn != null) {
+                if (conn != null) {
                     logi("onCallStartFailed eccCategory=" + eccCategory);
-                    if (reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL
-                            || reasonInfo.getExtraCode()
-                                    == ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY) {
+                    int reason = reasonInfo.getCode();
+                    int extraCode = reasonInfo.getExtraCode();
+                    if ((reason == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
+                            && extraCode == ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY)
+                            || (reason == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL)) {
                         conn.setNonDetectableEmergencyCallInfo(eccCategory);
                     }
                     conn.setImsReasonInfo(reasonInfo);
diff --git a/src/java/com/android/internal/telephony/metrics/CellularSecurityTransparencyStats.java b/src/java/com/android/internal/telephony/metrics/CellularSecurityTransparencyStats.java
new file mode 100644
index 0000000..f77474f
--- /dev/null
+++ b/src/java/com/android/internal/telephony/metrics/CellularSecurityTransparencyStats.java
@@ -0,0 +1,86 @@
+/*
+ * 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.metrics;
+
+import android.telephony.CellularIdentifierDisclosure;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.TelephonyStatsLog;
+import com.android.telephony.Rlog;
+
+/**
+ * Facilitates writing stats relating to cellular transparency features. Delegates the actual
+ * writing of stats out to {@link TelephonyStatsLog}.
+ */
+public class CellularSecurityTransparencyStats {
+
+    private static final String LOG_TAG = "CellularSecurityTransparencyStats";
+    private static final String LOG_DESCRIPTOR_SIM_MCC = "SIM MCC";
+    private static final String LOG_DESCRIPTOR_SIM_MNC = "SIM MNC";
+    private static final String LOG_DESCRIPTOR_DISCLOSURE_MCC = "disclosure MCC";
+    private static final String LOG_DESCRIPTOR_DISCLOSURE_MNC = "disclosure MNC";
+    private static final int DEFAULT_PLMN_PART = -1;
+
+    /**
+     * Log an identifier disclosure to be written out to {@link TelephonyStatsLog}
+     */
+    public void logIdentifierDisclosure(CellularIdentifierDisclosure disclosure, String simMcc,
+            String simMnc, boolean notificationsEnabled) {
+
+        int mcc = parsePlmnPartOrDefault(simMcc, LOG_DESCRIPTOR_SIM_MCC);
+        int mnc = parsePlmnPartOrDefault(simMnc, LOG_DESCRIPTOR_SIM_MNC);
+
+        int disclosureMcc = DEFAULT_PLMN_PART;
+        int disclosureMnc = DEFAULT_PLMN_PART;
+        String plmn = disclosure.getPlmn();
+        if (plmn != null) {
+            String[] plmnParts = plmn.split("-");
+            if (plmnParts.length == 2) {
+                disclosureMcc = parsePlmnPartOrDefault(plmnParts[0], LOG_DESCRIPTOR_DISCLOSURE_MCC);
+                disclosureMnc = parsePlmnPartOrDefault(plmnParts[1], LOG_DESCRIPTOR_DISCLOSURE_MNC);
+            }
+        }
+
+        writeIdentifierDisclosure(mcc, mnc, disclosureMcc, disclosureMnc,
+                disclosure.getCellularIdentifier(), disclosure.getNasProtocolMessage(),
+                disclosure.isEmergency(), notificationsEnabled);
+
+    }
+
+    private int parsePlmnPartOrDefault(String input, String logDescriptor) {
+        try {
+            return Integer.parseInt(input);
+        } catch (NumberFormatException e) {
+            Rlog.d(LOG_TAG, "Failed to parse " + logDescriptor + ": " + input);
+        }
+
+        return DEFAULT_PLMN_PART;
+    }
+
+    /**
+     * Write identifier disclosure data out to {@link TelephonyStatsLog}. This method is split
+     * out to enable testing, since {@link TelephonyStatsLog} is a final static class.
+     */
+    @VisibleForTesting
+    public void writeIdentifierDisclosure(int mcc, int mnc, int disclosureMcc, int disclosureMnc,
+            int identifier, int protocolMessage, boolean isEmergency,
+            boolean areNotificationsEnabled) {
+        TelephonyStatsLog.write(TelephonyStatsLog.CELLULAR_IDENTIFIER_DISCLOSED, mcc, mnc,
+                disclosureMcc, disclosureMnc, identifier, protocolMessage, isEmergency,
+                areNotificationsEnabled);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java b/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
index 387495e..cd5b7d6 100644
--- a/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
+++ b/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
@@ -16,12 +16,27 @@
 
 package com.android.internal.telephony.metrics;
 
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_SUCCEEDED_BITMASK;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_VALIDATION_RESULT;
+import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS;
+import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE;
+import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_DNS_CONSECUTIVE_TIMEOUTS;
+
+import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.ConnectivityDiagnosticsManager;
+import android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
+import android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import android.net.ConnectivityDiagnosticsManager.DataStallReport;
 import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.PersistableBundle;
+import android.os.SystemClock;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation.NetworkType;
 import android.telephony.CellSignalStrength;
@@ -30,6 +45,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataCallResponse.LinkStatus;
+import android.text.TextUtils;
 
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
@@ -38,11 +54,14 @@
 import com.android.internal.telephony.data.DataNetworkController;
 import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
 import com.android.internal.telephony.data.DataStallRecoveryManager;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.telephony.Rlog;
 
 import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Generates metrics related to data stall recovery events per phone ID for the pushed atom.
@@ -57,18 +76,31 @@
 
     private static final String TAG = "DSRS-";
 
+    private static final int UNSET_DIAGNOSTIC_STATE = -1;
+
+    private static final long REFRESH_DURATION_IN_MILLIS = TimeUnit.MINUTES.toMillis(3);
+
     // Handler to upload metrics.
     private final @NonNull Handler mHandler;
 
     private final @NonNull String mTag;
     private final @NonNull Phone mPhone;
+    private final @NonNull TelephonyManager mTelephonyManager;
+    private final @NonNull FeatureFlags mFeatureFlags;
+
+    // Flag to control the DSRS diagnostics
+    private final boolean mIsDsrsDiagnosticsEnabled;
 
     // The interface name of the internet network.
     private @Nullable String mIfaceName = null;
 
     /* Metrics and stats data variables */
+    // Record metrics refresh time in milliseconds to decide whether to refresh data again
+    @ElapsedRealtimeLong
+    private long mMetricsReflashTime = 0L;
     private int mPhoneId = 0;
     private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+    private int mConvertedMccMnc = -1;
     private int mSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
     private int mBand = 0;
     // The RAT used for data (including IWLAN).
@@ -88,17 +120,33 @@
     private int mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
     private int mLinkUpBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
 
+    // Connectivity diagnostics states
+    private int mNetworkProbesResult = UNSET_DIAGNOSTIC_STATE;
+    private int mNetworkProbesType = UNSET_DIAGNOSTIC_STATE;
+    private int mNetworkValidationResult = UNSET_DIAGNOSTIC_STATE;
+    private int mTcpMetricsCollectionPeriodMillis = UNSET_DIAGNOSTIC_STATE;
+    private int mTcpPacketFailRate = UNSET_DIAGNOSTIC_STATE;
+    private int mDnsConsecutiveTimeouts = UNSET_DIAGNOSTIC_STATE;
+
+    private ConnectivityDiagnosticsManager mConnectivityDiagnosticsManager = null;
+    private ConnectivityDiagnosticsCallback mConnectivityDiagnosticsCallback = null;
+    private static final Executor INLINE_EXECUTOR = x -> x.run();
+
     /**
      * Constructs a new instance of {@link DataStallRecoveryStats}.
      */
-    public DataStallRecoveryStats(@NonNull final Phone phone,
+    public DataStallRecoveryStats(
+            @NonNull final Phone phone,
+            @NonNull FeatureFlags featureFlags,
             @NonNull final DataNetworkController dataNetworkController) {
         mTag = TAG + phone.getPhoneId();
         mPhone = phone;
+        mFeatureFlags = featureFlags;
 
         HandlerThread handlerThread = new HandlerThread(mTag + "-thread");
         handlerThread.start();
         mHandler = new Handler(handlerThread.getLooper());
+        mTelephonyManager = mPhone.getContext().getSystemService(TelephonyManager.class);
 
         dataNetworkController.registerDataNetworkControllerCallback(
                 new DataNetworkControllerCallback(mHandler::post) {
@@ -117,6 +165,45 @@
                     mInternetLinkStatus = status;
                 }
             });
+
+        mIsDsrsDiagnosticsEnabled = mFeatureFlags.dsrsDiagnosticsEnabled();
+        if (mIsDsrsDiagnosticsEnabled) {
+            try {
+                // Register ConnectivityDiagnosticsCallback to get diagnostics states
+                mConnectivityDiagnosticsManager =
+                    mPhone.getContext().getSystemService(ConnectivityDiagnosticsManager.class);
+                mConnectivityDiagnosticsCallback = new ConnectivityDiagnosticsCallback() {
+                    @Override
+                    public void onConnectivityReportAvailable(@NonNull ConnectivityReport report) {
+                        PersistableBundle bundle = report.getAdditionalInfo();
+                        mNetworkProbesResult = bundle.getInt(KEY_NETWORK_PROBES_SUCCEEDED_BITMASK);
+                        mNetworkProbesType = bundle.getInt(KEY_NETWORK_PROBES_ATTEMPTED_BITMASK);
+                        mNetworkValidationResult = bundle.getInt(KEY_NETWORK_VALIDATION_RESULT);
+                    }
+
+                    @Override
+                    public void onDataStallSuspected(@NonNull DataStallReport report) {
+                        PersistableBundle bundle = report.getStallDetails();
+                        mTcpMetricsCollectionPeriodMillis =
+                            bundle.getInt(KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS);
+                        mTcpPacketFailRate = bundle.getInt(KEY_TCP_PACKET_FAIL_RATE);
+                        mDnsConsecutiveTimeouts = bundle.getInt(KEY_DNS_CONSECUTIVE_TIMEOUTS);
+                    }
+                };
+                mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback(
+                    new NetworkRequest.Builder()
+                        .clearCapabilities()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                        .build(),
+                        INLINE_EXECUTOR,
+                        mConnectivityDiagnosticsCallback
+                );
+            } catch (Exception e) {
+                mConnectivityDiagnosticsManager = null;
+                mConnectivityDiagnosticsCallback = null;
+            }
+        }
     }
 
     /**
@@ -194,10 +281,26 @@
      */
     private void refreshMetricsData() {
         logd("Refreshes the metrics data.");
+        // Update the metrics reflash time
+        mMetricsReflashTime = SystemClock.elapsedRealtime();
         // Update phone id/carrier id and signal strength
         mPhoneId = mPhone.getPhoneId() + 1;
         mCarrierId = mPhone.getCarrierId();
         mSignalStrength = mPhone.getSignalStrength().getLevel();
+        if (mIsDsrsDiagnosticsEnabled) {
+            // Get the MCCMNC and convert it to an int
+            String networkOperator = mTelephonyManager.getNetworkOperator();
+            if (!TextUtils.isEmpty(networkOperator)) {
+                try {
+                    mConvertedMccMnc = Integer.parseInt(networkOperator);
+                } catch (NumberFormatException e) {
+                    loge("Invalid MCCMNC format: " + networkOperator);
+                    mConvertedMccMnc = -1;
+                }
+            } else {
+                mConvertedMccMnc = -1;
+            }
+        }
 
         // Update the bandwidth.
         updateBandwidths();
@@ -308,7 +411,8 @@
      * @param isRecovered Whether the data stall has been recovered.
      * @param duration The duration from data stall occurred in milliseconds.
      * @param reason The reason for the recovery.
-     * @param isFirstValidation Whether this is the first validation after recovery.
+     * @param validationCount The total number of validation duration a data stall.
+     * @param actionValidationCount The number of validation for current action during a data stall
      * @param durationOfAction The duration of the current action in milliseconds.
      */
     public Bundle getDataStallRecoveryMetricsData(
@@ -316,28 +420,75 @@
             boolean isRecovered,
             int duration,
             @DataStallRecoveryManager.RecoveredReason int reason,
-            boolean isFirstValidation,
+            int validationCount,
+            int actionValidationCount,
             int durationOfAction) {
+
+        if (mIsDsrsDiagnosticsEnabled) {
+            // Refresh data if the data has not been updated within 3 minutes
+            final long refreshDuration = SystemClock.elapsedRealtime() - mMetricsReflashTime;
+            if (refreshDuration > REFRESH_DURATION_IN_MILLIS) {
+                // Refreshes the metrics data.
+                try {
+                    refreshMetricsData();
+                } catch (Exception e) {
+                    loge("The metrics data cannot be refreshed.", e);
+                }
+            }
+        }
+
         Bundle bundle = new Bundle();
-        bundle.putInt("Action", action);
-        bundle.putBoolean("IsRecovered", isRecovered);
-        bundle.putInt("Duration", duration);
-        bundle.putInt("Reason", reason);
-        bundle.putBoolean("IsFirstValidation", isFirstValidation);
-        bundle.putInt("DurationOfAction", durationOfAction);
-        bundle.putInt("PhoneId", mPhoneId);
-        bundle.putInt("CarrierId", mCarrierId);
-        bundle.putInt("SignalStrength", mSignalStrength);
-        bundle.putInt("Band", mBand);
-        bundle.putInt("Rat", mRat);
-        bundle.putBoolean("IsOpportunistic", mIsOpportunistic);
-        bundle.putBoolean("IsMultiSim", mIsMultiSim);
-        bundle.putInt("NetworkRegState", mNetworkRegState);
-        bundle.putInt("OtherSignalStrength", mOtherSignalStrength);
-        bundle.putInt("OtherNetworkRegState", mOtherNetworkRegState);
-        bundle.putInt("InternetLinkStatus", mInternetLinkStatus);
-        bundle.putInt("LinkDownBandwidthKbps", mLinkDownBandwidthKbps);
-        bundle.putInt("LinkUpBandwidthKbps", mLinkUpBandwidthKbps);
+
+        if (mIsDsrsDiagnosticsEnabled) {
+            bundle.putInt("Action", action);
+            bundle.putInt("IsRecovered", isRecovered ? 1 : 0);
+            bundle.putInt("Duration", duration);
+            bundle.putInt("Reason", reason);
+            bundle.putInt("DurationOfAction", durationOfAction);
+            bundle.putInt("ValidationCount", validationCount);
+            bundle.putInt("ActionValidationCount", actionValidationCount);
+            bundle.putInt("PhoneId", mPhoneId);
+            bundle.putInt("CarrierId", mCarrierId);
+            bundle.putInt("MccMnc", mConvertedMccMnc);
+            bundle.putInt("SignalStrength", mSignalStrength);
+            bundle.putInt("Band", mBand);
+            bundle.putInt("Rat", mRat);
+            bundle.putInt("IsOpportunistic", mIsOpportunistic ? 1 : 0);
+            bundle.putInt("IsMultiSim", mIsMultiSim ? 1 : 0);
+            bundle.putInt("NetworkRegState", mNetworkRegState);
+            bundle.putInt("OtherSignalStrength", mOtherSignalStrength);
+            bundle.putInt("OtherNetworkRegState", mOtherNetworkRegState);
+            bundle.putInt("InternetLinkStatus", mInternetLinkStatus);
+            bundle.putInt("LinkDownBandwidthKbps", mLinkDownBandwidthKbps);
+            bundle.putInt("LinkUpBandwidthKbps", mLinkUpBandwidthKbps);
+            bundle.putInt("NetworkProbesResult", mNetworkProbesResult);
+            bundle.putInt("NetworkProbesType", mNetworkProbesType);
+            bundle.putInt("NetworkValidationResult", mNetworkValidationResult);
+            bundle.putInt("TcpMetricsCollectionPeriodMillis", mTcpMetricsCollectionPeriodMillis);
+            bundle.putInt("TcpPacketFailRate", mTcpPacketFailRate);
+            bundle.putInt("DnsConsecutiveTimeouts", mDnsConsecutiveTimeouts);
+        } else {
+            bundle.putInt("Action", action);
+            bundle.putBoolean("IsRecovered", isRecovered);
+            bundle.putInt("Duration", duration);
+            bundle.putInt("Reason", reason);
+            bundle.putBoolean("IsFirstValidation", validationCount == 1);
+            bundle.putInt("DurationOfAction", durationOfAction);
+            bundle.putInt("PhoneId", mPhoneId);
+            bundle.putInt("CarrierId", mCarrierId);
+            bundle.putInt("SignalStrength", mSignalStrength);
+            bundle.putInt("Band", mBand);
+            bundle.putInt("Rat", mRat);
+            bundle.putBoolean("IsOpportunistic", mIsOpportunistic);
+            bundle.putBoolean("IsMultiSim", mIsMultiSim);
+            bundle.putInt("NetworkRegState", mNetworkRegState);
+            bundle.putInt("OtherSignalStrength", mOtherSignalStrength);
+            bundle.putInt("OtherNetworkRegState", mOtherNetworkRegState);
+            bundle.putInt("InternetLinkStatus", mInternetLinkStatus);
+            bundle.putInt("LinkDownBandwidthKbps", mLinkDownBandwidthKbps);
+            bundle.putInt("LinkUpBandwidthKbps", mLinkUpBandwidthKbps);
+        }
+
         return bundle;
     }
 
diff --git a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
index a315f1e..856045f 100644
--- a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
+++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
@@ -138,14 +138,6 @@
     private static final long MIN_COOLDOWN_MILLIS =
             DBG ? 10L * MILLIS_PER_SECOND : 23L * MILLIS_PER_HOUR;
 
-    /**
-     * Sets atom pull cool down to 4 minutes for userdebug build.
-     *
-     * <p>Applies to certain atoms: CellularServiceState.
-     */
-    private static final long CELL_SERVICE_MIN_COOLDOWN_MILLIS =
-            DBG ? 10L * MILLIS_PER_SECOND :
-                    IS_DEBUGGABLE ? 4L * MILLIS_PER_MINUTE : 23L * MILLIS_PER_HOUR;
 
     /**
      * Buckets with less than these many calls will be dropped.
@@ -166,6 +158,13 @@
     private static final long CELL_SERVICE_DURATION_BUCKET_MILLIS =
             DBG || IS_DEBUGGABLE ? 2L * MILLIS_PER_SECOND : 5L * MILLIS_PER_MINUTE;
 
+    /**
+     * Sets atom pull cool down to 4 minutes for userdebug build, 5 hours for user build.
+     *
+     * <p>Applies to certain atoms: CellularServiceState, DataCallSession,
+     * ImsRegistrationTermination.
+     */
+    private final long mPowerCorrelatedMinCooldownMillis;
     private final PersistAtomsStorage mStorage;
     private final DeviceStateHelper mDeviceStateHelper;
     private final StatsManager mStatsManager;
@@ -234,6 +233,9 @@
 
         mAirplaneModeStats = new AirplaneModeStats(context);
         mDefaultNetworkMonitor = new DefaultNetworkMonitor(context, featureFlags);
+        mPowerCorrelatedMinCooldownMillis = DBG ? 10L * MILLIS_PER_SECOND :
+                IS_DEBUGGABLE ? 4L * MILLIS_PER_MINUTE : (long) context.getResources().getInteger(
+                com.android.internal.R.integer.config_metrics_pull_cooldown_millis);
     }
 
     /**
@@ -516,7 +518,8 @@
     private int pullDataCallSession(List<StatsEvent> data) {
         // Include ongoing data call segments
         concludeDataCallSessionStats();
-        DataCallSession[] dataCallSessions = mStorage.getDataCallSessions(MIN_COOLDOWN_MILLIS);
+        DataCallSession[] dataCallSessions = mStorage.getDataCallSessions(
+                mPowerCorrelatedMinCooldownMillis);
         if (dataCallSessions != null) {
             Arrays.stream(dataCallSessions)
                     .forEach(dataCall -> data.add(buildStatsEvent(dataCall)));
@@ -544,8 +547,8 @@
     private int pullCellularServiceState(List<StatsEvent> data) {
         // Include the latest durations
         concludeServiceStateStats();
-        CellularServiceState[] persistAtoms =
-                mStorage.getCellularServiceStates(CELL_SERVICE_MIN_COOLDOWN_MILLIS);
+        CellularServiceState[] persistAtoms = mStorage.getCellularServiceStates(
+                mPowerCorrelatedMinCooldownMillis);
         if (persistAtoms != null) {
             // list is already shuffled when instances were inserted
             Arrays.stream(persistAtoms)
@@ -573,8 +576,8 @@
     }
 
     private int pullImsRegistrationTermination(List<StatsEvent> data) {
-        ImsRegistrationTermination[] persistAtoms =
-                mStorage.getImsRegistrationTerminations(MIN_COOLDOWN_MILLIS);
+        ImsRegistrationTermination[] persistAtoms = mStorage.getImsRegistrationTerminations(
+                mPowerCorrelatedMinCooldownMillis);
         if (persistAtoms != null) {
             // list is already shuffled when instances were inserted
             Arrays.stream(persistAtoms)
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index 55db6d2..92d9372 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -93,6 +93,7 @@
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.uwb.UwbManager;
@@ -2518,6 +2519,12 @@
             logd("getSatellitePlmnsForCarrier: carrierEnabledSatelliteFlag is disabled");
             return new ArrayList<>();
         }
+
+        if (!isSatelliteSupportedViaCarrier(subId)) {
+            logd("Satellite for carrier is not supported.");
+            return new ArrayList<>();
+        }
+
         synchronized (mSupportedSatelliteServicesLock) {
             return mMergedPlmnListPerCarrier.get(subId, new ArrayList<>()).stream().toList();
         }
@@ -2596,21 +2603,21 @@
     }
 
     /**
-     * @return {@code true} if any subscription on the device is connected to satellite,
-     * {@code false} otherwise.
+     * @return {@code Pair<true, subscription ID>} if any subscription on the device is connected to
+     * satellite, {@code Pair<false, null>} otherwise.
      */
-    private boolean isUsingNonTerrestrialNetworkViaCarrier() {
+    private Pair<Boolean, Integer> isUsingNonTerrestrialNetworkViaCarrier() {
         if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
             logd("isUsingNonTerrestrialNetwork: carrierEnabledSatelliteFlag is disabled");
-            return false;
+            return new Pair<>(false, null);
         }
         for (Phone phone : PhoneFactory.getPhones()) {
             ServiceState serviceState = phone.getServiceState();
             if (serviceState != null && serviceState.isUsingNonTerrestrialNetwork()) {
-                return true;
+                return new Pair<>(true, phone.getSubId());
             }
         }
-        return false;
+        return new Pair<>(false, null);
     }
 
     /**
@@ -2624,7 +2631,7 @@
                     + " is disabled");
             return false;
         }
-        if (isUsingNonTerrestrialNetworkViaCarrier()) {
+        if (isUsingNonTerrestrialNetworkViaCarrier().first) {
             return true;
         }
         for (Phone phone : PhoneFactory.getPhones()) {
@@ -2872,6 +2879,7 @@
      * @return true if satellite is provisioned on the given subscription else return false.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @Nullable
     protected Boolean isSatelliteViaOemProvisioned() {
         synchronized (mSatelliteViaOemProvisionLock) {
             if (mOverriddenIsSatelliteViaOemProvisioned != null) {
@@ -3995,7 +4003,13 @@
     }
 
     private void determineSystemNotification() {
-        if (isUsingNonTerrestrialNetworkViaCarrier()) {
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            logd("determineSystemNotification: carrierEnabledSatelliteFlag is disabled");
+            return;
+        }
+
+        Pair<Boolean, Integer> isNtn = isUsingNonTerrestrialNetworkViaCarrier();
+        if (isNtn.first) {
             if (mSharedPreferences == null) {
                 try {
                     mSharedPreferences = mContext.getSharedPreferences(SATELLITE_SHARED_PREF,
@@ -4005,18 +4019,18 @@
                 }
             }
             if (mSharedPreferences == null) {
-                loge("handleEventServiceStateChanged: Cannot get default shared preferences");
+                loge("determineSystemNotification: Cannot get default shared preferences");
                 return;
             }
             if (!mSharedPreferences.getBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY, false)) {
-                showSatelliteSystemNotification();
+                showSatelliteSystemNotification(isNtn.second);
                 mSharedPreferences.edit().putBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY,
                         true).apply();
             }
         }
     }
 
-    private void showSatelliteSystemNotification() {
+    private void showSatelliteSystemNotification(int subId) {
         logd("showSatelliteSystemNotification");
         final NotificationChannel notificationChannel = new NotificationChannel(
                 NOTIFICATION_CHANNEL_ID,
@@ -4054,6 +4068,7 @@
 
         // Add action to invoke Satellite setting activity in Settings.
         Intent intentSatelliteSetting = new Intent(ACTION_SATELLITE_SETTING);
+        intentSatelliteSetting.putExtra("sub_id", subId);
         PendingIntent pendingIntentSatelliteSetting = PendingIntent.getActivity(mContext, 0,
                 intentSatelliteSetting, PendingIntent.FLAG_IMMUTABLE);
 
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
index 149b054..c491476 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
@@ -309,8 +309,14 @@
         }
     }
 
-    private boolean isSatelliteViaOemAvailable() {
-        return mSatelliteController.isSatelliteViaOemProvisioned();
+    /**
+     * Check if satellite is available via OEM
+     * @return {@code true} if satellite is provisioned via OEM else return {@code false}
+     */
+    @VisibleForTesting
+    public boolean isSatelliteViaOemAvailable() {
+        Boolean satelliteProvisioned = mSatelliteController.isSatelliteViaOemProvisioned();
+        return satelliteProvisioned != null ? satelliteProvisioned : false;
     }
 
     private boolean isSatelliteViaCarrierAvailable() {
diff --git a/src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java b/src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java
index 4540b8a..8591e86 100644
--- a/src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java
+++ b/src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java
@@ -21,6 +21,9 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.metrics.CellularSecurityTransparencyStats;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.telephony.Rlog;
 
 import java.time.Instant;
@@ -62,13 +65,13 @@
     // This object should only be accessed from within the thread of mSerializedWorkQueue. Access
     // outside of that thread would require additional synchronization.
     private Map<Integer, DisclosureWindow> mWindows;
+    private SubscriptionManagerService mSubscriptionManagerService;
+    private CellularSecurityTransparencyStats mCellularSecurityTransparencyStats;
 
     public CellularIdentifierDisclosureNotifier(CellularNetworkSecuritySafetySource safetySource) {
-        this(
-                Executors.newSingleThreadScheduledExecutor(),
-                DEFAULT_WINDOW_CLOSE_DURATION_IN_MINUTES,
-                TimeUnit.MINUTES,
-                safetySource);
+        this(Executors.newSingleThreadScheduledExecutor(), DEFAULT_WINDOW_CLOSE_DURATION_IN_MINUTES,
+                TimeUnit.MINUTES, safetySource, SubscriptionManagerService.getInstance(),
+                new CellularSecurityTransparencyStats());
     }
 
     /**
@@ -83,12 +86,16 @@
             ScheduledExecutorService notificationQueue,
             long windowCloseDuration,
             TimeUnit windowCloseUnit,
-            CellularNetworkSecuritySafetySource safetySource) {
+            CellularNetworkSecuritySafetySource safetySource,
+            SubscriptionManagerService subscriptionManagerService,
+            CellularSecurityTransparencyStats cellularSecurityTransparencyStats) {
         mSerializedWorkQueue = notificationQueue;
         mWindowCloseDuration = windowCloseDuration;
         mWindowCloseUnit = windowCloseUnit;
         mWindows = new HashMap<>();
         mSafetySource = safetySource;
+        mSubscriptionManagerService = subscriptionManagerService;
+        mCellularSecurityTransparencyStats = cellularSecurityTransparencyStats;
     }
 
     /**
@@ -98,6 +105,8 @@
     public void addDisclosure(Context context, int subId, CellularIdentifierDisclosure disclosure) {
         Rlog.d(TAG, "Identifier disclosure reported: " + disclosure);
 
+        logDisclosure(subId, disclosure);
+
         synchronized (mEnabledLock) {
             if (!mEnabled) {
                 Rlog.d(TAG, "Skipping disclosure because notifier was disabled.");
@@ -123,6 +132,31 @@
         } // end mEnabledLock
     }
 
+    private void logDisclosure(int subId, CellularIdentifierDisclosure disclosure) {
+        try {
+            mSerializedWorkQueue.execute(runLogDisclosure(subId, disclosure));
+        } catch (RejectedExecutionException e) {
+            Rlog.e(TAG, "Failed to schedule runLogDisclosure: " + e.getMessage());
+        }
+    }
+
+    private Runnable runLogDisclosure(int subId,
+            CellularIdentifierDisclosure disclosure) {
+        return () -> {
+            SubscriptionInfoInternal subInfo =
+                    mSubscriptionManagerService.getSubscriptionInfoInternal(subId);
+            String mcc = null;
+            String mnc = null;
+            if (subInfo != null) {
+                mcc = subInfo.getMcc();
+                mnc = subInfo.getMnc();
+            }
+
+            mCellularSecurityTransparencyStats.logIdentifierDisclosure(disclosure, mcc, mnc,
+                    isEnabled());
+        };
+    }
+
     /**
      * Re-enable if previously disabled. This means that {@code addDisclsoure} will start tracking
      * disclosures again and potentially emitting notifications.
diff --git a/src/java/com/android/internal/telephony/security/NullCipherNotifier.java b/src/java/com/android/internal/telephony/security/NullCipherNotifier.java
index 0ab8299..3ece701 100644
--- a/src/java/com/android/internal/telephony/security/NullCipherNotifier.java
+++ b/src/java/com/android/internal/telephony/security/NullCipherNotifier.java
@@ -16,10 +16,29 @@
 
 package com.android.internal.telephony.security;
 
-import android.telephony.SecurityAlgorithmUpdate;
+import static android.telephony.SecurityAlgorithmUpdate.SECURITY_ALGORITHM_UNKNOWN;
 
+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 android.annotation.IntDef;
+import android.content.Context;
+import android.telephony.SecurityAlgorithmUpdate;
+import android.telephony.SecurityAlgorithmUpdate.ConnectionEvent;
+import android.telephony.SecurityAlgorithmUpdate.SecurityAlgorithm;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.telephony.Rlog;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+
 /**
  * Encapsulates logic to emit notifications to the user that a null cipher is in use. A null cipher
  * is one that does not attempt to implement any encryption.
@@ -27,44 +46,84 @@
  * <p>This class will either emit notifications through SafetyCenterManager if SafetyCenter exists
  * on a device, or it will emit system notifications otherwise.
  *
+ * TODO(b/319662115): handle radio availability, no service, SIM removal, etc.
+ *
  * @hide
  */
 public class NullCipherNotifier {
-
     private static final String TAG = "NullCipherNotifier";
     private static NullCipherNotifier sInstance;
 
+    private final CellularNetworkSecuritySafetySource mSafetySource;
+    private final HashMap<Integer, SubscriptionState> mSubscriptionState = new HashMap<>();
+
+    private final Object mEnabledLock = new Object();
+    @GuardedBy("mEnabledLock")
     private boolean mEnabled = false;
 
+    // This is a single threaded executor. This is important because we want to ensure certain
+    // events are strictly serialized.
+    private ScheduledExecutorService mSerializedWorkQueue;
+
     /**
      * Gets a singleton NullCipherNotifier.
      */
-    public static synchronized NullCipherNotifier getInstance() {
+    public static synchronized NullCipherNotifier getInstance(
+            CellularNetworkSecuritySafetySource safetySource) {
         if (sInstance == null) {
-            sInstance = new NullCipherNotifier();
+            sInstance = new NullCipherNotifier(
+                    Executors.newSingleThreadScheduledExecutor(),
+                    safetySource);
         }
         return sInstance;
     }
 
-    private NullCipherNotifier() {}
+    @VisibleForTesting
+    public NullCipherNotifier(
+            ScheduledExecutorService notificationQueue,
+            CellularNetworkSecuritySafetySource safetySource) {
+        mSerializedWorkQueue = notificationQueue;
+        mSafetySource = safetySource;
+    }
 
     /**
      * Adds a security algorithm update. If appropriate, this will trigger a user notification.
      */
-    public void onSecurityAlgorithmUpdate(int phoneId, SecurityAlgorithmUpdate update) {
-        // TODO (b/315005938) this is a stub method for now. Logic
-        // for tracking disclosures and emitting notifications will flow
-        // from here.
-        Rlog.d(TAG, "Security algorithm update: phoneId = " + phoneId + " " + update);
+    public void onSecurityAlgorithmUpdate(
+            Context context, int subId, SecurityAlgorithmUpdate update) {
+        Rlog.d(TAG, "Security algorithm update: subId = " + subId + " " + update);
+
+        if (shouldIgnoreUpdate(update)) {
+            return;
+        }
+
+        try {
+            mSerializedWorkQueue.execute(() -> {
+                SubscriptionState subState = mSubscriptionState.get(subId);
+                if (subState == null) {
+                    subState = new SubscriptionState();
+                    mSubscriptionState.put(subId, subState);
+                }
+
+                @CellularNetworkSecuritySafetySource.NullCipherState int nullCipherState =
+                        subState.update(update);
+                mSafetySource.setNullCipherState(context, subId, nullCipherState);
+            });
+        } catch (RejectedExecutionException e) {
+            Rlog.e(TAG, "Failed to schedule onEnableNotifier: " + e.getMessage());
+        }
     }
 
     /**
      * Enables null cipher notification; {@code onSecurityAlgorithmUpdate} will start handling
      * security algorithm updates and send notifications to the user when required.
      */
-    public void enable() {
-        Rlog.d(TAG, "enabled");
-        mEnabled = true;
+    public void enable(Context context) {
+        synchronized (mEnabledLock) {
+            Rlog.d(TAG, "enabled");
+            mEnabled = true;
+            scheduleOnEnabled(context, true);
+        }
     }
 
     /**
@@ -73,12 +132,180 @@
      * {@code onSecurityAlgorithmUpdate} is called while in a disabled state, security algorithm
      * updates will be dropped.
      */
-    public void disable() {
-        Rlog.d(TAG, "disabled");
-        mEnabled = false;
+    public void disable(Context context) {
+        synchronized (mEnabledLock) {
+            Rlog.d(TAG, "disabled");
+            mEnabled = false;
+            scheduleOnEnabled(context, false);
+        }
     }
 
+    /** Checks whether the null cipher notification is enabled. */
     public boolean isEnabled() {
-        return mEnabled;
+        synchronized (mEnabledLock) {
+            return mEnabled;
+        }
     }
+
+    private void scheduleOnEnabled(Context context, boolean enabled) {
+        try {
+            mSerializedWorkQueue.execute(() -> {
+                Rlog.i(TAG, "On enable notifier. Enable value: " + enabled);
+                mSafetySource.setNullCipherIssueEnabled(context, enabled);
+            });
+        } catch (RejectedExecutionException e) {
+            Rlog.e(TAG, "Failed to schedule onEnableNotifier: " + e.getMessage());
+        }
+
+    }
+
+    /** Returns whether the update should be dropped and the monitoring state left unchanged. */
+    private static boolean shouldIgnoreUpdate(SecurityAlgorithmUpdate update) {
+        // Ignore emergencies.
+        if (update.isUnprotectedEmergency()) {
+            return true;
+        }
+
+        switch (update.getConnectionEvent()) {
+            // Ignore non-link layer protocols. Monitoring is only looking for data exposed
+            // over-the-air so only the link layer protocols are tracked. Higher-level protocols can
+            // protect data further into the network but that is out of scope.
+            case SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_SIP:
+            case SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_RTP:
+            case SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_SIP:
+            case SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_RTP:
+            // Ignore emergencies.
+            case SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_SIP_SOS:
+            case SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_RTP_SOS:
+            case SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_SIP_SOS:
+            case SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_RTP_SOS:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Determines whether an algorithm does not attempt to implement any encryption and is therefore
+     * considered a null cipher.
+     *
+     * <p>Only the algorithms known to be null ciphers are classified as such. Explicitly unknown
+     * algorithms, or algorithms that are unknown by means of values added to newer HALs, are
+     * assumed not to be null ciphers.
+     */
+    private static boolean isNullCipher(@SecurityAlgorithm int algorithm) {
+        switch (algorithm) {
+            case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_A50:
+            case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_GEA0:
+            case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_UEA0:
+            case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA0:
+            case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_NEA0:
+            case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_IMS_NULL:
+            case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SIP_NULL:
+            case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SRTP_NULL:
+            case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_OTHER:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /** The state of network connections for a subscription. */
+    private static final class SubscriptionState {
+        private @NetworkClass int mActiveNetworkClass = NETWORK_CLASS_UNKNOWN;
+        private final HashMap<Integer, ConnectionState> mState = new HashMap<>();
+
+        private @CellularNetworkSecuritySafetySource.NullCipherState int
+                update(SecurityAlgorithmUpdate update) {
+            boolean fromNullCipherState = hasNullCipher();
+
+            @NetworkClass int networkClass = getNetworkClass(update.getConnectionEvent());
+            if (networkClass != mActiveNetworkClass || networkClass == NETWORK_CLASS_UNKNOWN) {
+                mState.clear();
+                mActiveNetworkClass = networkClass;
+            }
+
+            ConnectionState fromState =
+                    mState.getOrDefault(update.getConnectionEvent(), ConnectionState.UNKNOWN);
+            ConnectionState toState = new ConnectionState(
+                    update.getEncryption() == SECURITY_ALGORITHM_UNKNOWN
+                            ? fromState.getEncryption()
+                            : update.getEncryption(),
+                    update.getIntegrity() == SECURITY_ALGORITHM_UNKNOWN
+                            ? fromState.getIntegrity()
+                            : update.getIntegrity());
+            mState.put(update.getConnectionEvent(), toState);
+
+            if (hasNullCipher()) {
+                return NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED;
+            }
+            if (!fromNullCipherState || mActiveNetworkClass == NETWORK_CLASS_UNKNOWN) {
+                return NULL_CIPHER_STATE_ENCRYPTED;
+            }
+            return NULL_CIPHER_STATE_NOTIFY_ENCRYPTED;
+        }
+
+        private boolean hasNullCipher() {
+            return mState.values().stream().anyMatch(ConnectionState::hasNullCipher);
+        }
+
+        private static final int NETWORK_CLASS_UNKNOWN = 0;
+        private static final int NETWORK_CLASS_2G = 2;
+        private static final int NETWORK_CLASS_3G = 3;
+        private static final int NETWORK_CLASS_4G = 4;
+        private static final int NETWORK_CLASS_5G = 5;
+
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = {"NETWORK_CLASS_"}, value = {NETWORK_CLASS_UNKNOWN,
+                NETWORK_CLASS_2G, NETWORK_CLASS_3G, NETWORK_CLASS_4G,
+                NETWORK_CLASS_5G})
+        private @interface NetworkClass {}
+
+        private static @NetworkClass int getNetworkClass(
+                @ConnectionEvent int connectionEvent) {
+            switch (connectionEvent) {
+                case SecurityAlgorithmUpdate.CONNECTION_EVENT_CS_SIGNALLING_GSM:
+                case SecurityAlgorithmUpdate.CONNECTION_EVENT_PS_SIGNALLING_GPRS:
+                    return NETWORK_CLASS_2G;
+                case SecurityAlgorithmUpdate.CONNECTION_EVENT_CS_SIGNALLING_3G:
+                case SecurityAlgorithmUpdate.CONNECTION_EVENT_PS_SIGNALLING_3G:
+                    return NETWORK_CLASS_3G;
+                case SecurityAlgorithmUpdate.CONNECTION_EVENT_NAS_SIGNALLING_LTE:
+                case SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_LTE:
+                    return NETWORK_CLASS_4G;
+                case SecurityAlgorithmUpdate.CONNECTION_EVENT_NAS_SIGNALLING_5G:
+                case SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_5G:
+                    return NETWORK_CLASS_5G;
+                default:
+                    return NETWORK_CLASS_UNKNOWN;
+            }
+        }
+    }
+
+    /** The state of security algorithms for a network connection. */
+    private static final class ConnectionState {
+        private static final ConnectionState UNKNOWN =
+                 new ConnectionState(SECURITY_ALGORITHM_UNKNOWN, SECURITY_ALGORITHM_UNKNOWN);
+
+        private final @SecurityAlgorithm int mEncryption;
+        private final @SecurityAlgorithm int mIntegrity;
+
+        private ConnectionState(
+                @SecurityAlgorithm int encryption, @SecurityAlgorithm int integrity) {
+            mEncryption = encryption;
+            mIntegrity = integrity;
+        }
+
+        private @SecurityAlgorithm int getEncryption() {
+            return mEncryption;
+        }
+
+        private @SecurityAlgorithm int getIntegrity() {
+            return mIntegrity;
+        }
+
+        private boolean hasNullCipher() {
+            return isNullCipher(mEncryption) || isNullCipher(mIntegrity);
+        }
+    };
 }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccProfile.java b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
index 0457971..a22f075 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccProfile.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
@@ -1290,6 +1290,11 @@
             return -1;
         }
 
+        if (mUiccApplications[index] == null) {
+            loge("App index " + index + " is invalid");
+            return -1;
+        }
+
         if (mUiccApplications[index].getType() != expectedAppType
                 && mUiccApplications[index].getType() != altExpectedAppType) {
             loge("App index " + index + " is invalid since it's not "
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
index e493a18..4fce070 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
@@ -3014,7 +3014,8 @@
                         new AsyncResult(null, update, null)));
         processAllMessages();
 
-        verify(mNullCipherNotifier, times(1)).onSecurityAlgorithmUpdate(eq(0), eq(update));
+        verify(mNullCipherNotifier, times(1))
+                .onSecurityAlgorithmUpdate(eq(mContext), eq(0), eq(update));
     }
 
     @Test
@@ -3067,7 +3068,7 @@
         setNullCipherNotificationPreferenceEnabled(true);
         phoneUT.handleNullCipherNotificationPreferenceChanged();
 
-        verify(mNullCipherNotifier, times(1)).enable();
+        verify(mNullCipherNotifier, times(1)).enable(eq(mContext));
         verify(mMockCi, times(1)).setSecurityAlgorithmsUpdatedEnabled(eq(true),
                 any(Message.class));
     }
@@ -3081,7 +3082,7 @@
         setNullCipherNotificationPreferenceEnabled(false);
         phoneUT.handleNullCipherNotificationPreferenceChanged();
 
-        verify(mNullCipherNotifier, times(1)).disable();
+        verify(mNullCipherNotifier, times(1)).disable(eq(mContext));
         verify(mMockCi, times(1)).setSecurityAlgorithmsUpdatedEnabled(eq(false),
                 any(Message.class));
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
index 776715c..41fd45a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
@@ -16,10 +16,12 @@
 
 package com.android.internal.telephony;
 
+import android.content.pm.PackageManager;
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.telephony.gsm.SmsMessage;
 import com.android.internal.util.HexDump;
@@ -28,6 +30,12 @@
 
 public class GsmSmsTest extends AndroidTestCase {
 
+    private boolean hasMessaging() {
+        final PackageManager pm = InstrumentationRegistry.getInstrumentation().getContext()
+                .getPackageManager();
+        return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING);
+    }
+
     @SmallTest
     public void testAddressing() throws Exception {
         String pdu = "07914151551512f2040B916105551511f100006060605130308A04D4F29C0E";
@@ -258,8 +266,8 @@
 
     @SmallTest
     public void testFragmentText() throws Exception {
-        boolean isGsmPhone = (TelephonyManager.getDefault().getPhoneType() ==
-                TelephonyManager.PHONE_TYPE_GSM);
+        boolean isGsmPhoneWithMessaging = (TelephonyManager.getDefault().getPhoneType()
+                == TelephonyManager.PHONE_TYPE_GSM) && hasMessaging();
 
         // Valid 160 character 7-bit text.
         String text = "123456789012345678901234567890123456789012345678901234567890" +
@@ -271,7 +279,7 @@
         assertEquals(1, ted.codeUnitSize);
         assertEquals(0, ted.languageTable);
         assertEquals(0, ted.languageShiftTable);
-        if (isGsmPhone) {
+        if (isGsmPhoneWithMessaging) {
             ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
             assertEquals(1, fragments.size());
         }
@@ -286,7 +294,7 @@
         assertEquals(1, ted.codeUnitSize);
         assertEquals(0, ted.languageTable);
         assertEquals(0, ted.languageShiftTable);
-        if (isGsmPhone) {
+        if (isGsmPhoneWithMessaging) {
             ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
             assertEquals(2, fragments.size());
             assertEquals(text, fragments.get(0) + fragments.get(1));
@@ -297,8 +305,8 @@
 
     @SmallTest
     public void testFragmentTurkishText() throws Exception {
-        boolean isGsmPhone = (TelephonyManager.getDefault().getPhoneType() ==
-                TelephonyManager.PHONE_TYPE_GSM);
+        boolean isGsmPhoneWithMessaging = (TelephonyManager.getDefault().getPhoneType()
+                == TelephonyManager.PHONE_TYPE_GSM) && hasMessaging();
 
         int[] oldTables = GsmAlphabet.getEnabledSingleShiftTables();
         int[] turkishTable = { 1 };
@@ -313,7 +321,7 @@
         assertEquals(1, ted.codeUnitSize);
         assertEquals(0, ted.languageTable);
         assertEquals(1, ted.languageShiftTable);
-        if (isGsmPhone) {
+        if (isGsmPhoneWithMessaging) {
             ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
             assertEquals(1, fragments.size());
             assertEquals(text, fragments.get(0));
@@ -329,7 +337,7 @@
         assertEquals(1, ted.codeUnitSize);
         assertEquals(0, ted.languageTable);
         assertEquals(1, ted.languageShiftTable);
-        if (isGsmPhone) {
+        if (isGsmPhoneWithMessaging) {
             ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
             assertEquals(2, fragments.size());
             assertEquals(text, fragments.get(0) + fragments.get(1));
@@ -347,7 +355,7 @@
         assertEquals(1, ted.codeUnitSize);
         assertEquals(0, ted.languageTable);
         assertEquals(1, ted.languageShiftTable);
-        if (isGsmPhone) {
+        if (isGsmPhoneWithMessaging) {
             ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
             assertEquals(3, fragments.size());
             assertEquals(text, fragments.get(0) + fragments.get(1) + fragments.get(2));
diff --git a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
index 4c68e26..a7e9604 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
@@ -255,7 +255,7 @@
         // Capture listener to emulate the carrier config change notification used later
         ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
-        mMultiSimSettingControllerUT = new MultiSimSettingController(mContext);
+        mMultiSimSettingControllerUT = new MultiSimSettingController(mContext, mFeatureFlags);
         processAllMessages();
         verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
                 listenerArgumentCaptor.capture());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
index 1ea989a..e08abd9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
@@ -1250,6 +1250,79 @@
     }
 
     @Test
+    public void testPrimaryTimerPrimaryCellChangeNrIdle() throws Exception {
+        doReturn(true).when(mFeatureFlags).supportNrSaRrcIdle();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        ArrayList<PhysicalChannelConfig> physicalChannelConfigs = new ArrayList<>();
+        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");
+        sendCarrierConfigChanged();
+
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // should trigger 10 second primary timer
+        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());
+
+        // change PCI during connected_rrc_idle
+        physicalChannelConfigs.add(new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(2)
+                .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_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // change PCI for the second time during connected_rrc_idle
+        physicalChannelConfigs.add(new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(3)
+                .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());
+
+        // primary timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
+    }
+
+    @Test
     public void testSecondaryTimerExpire() throws Exception {
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
         mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
@@ -1477,7 +1550,7 @@
                 mNetworkTypeController.getOverrideNetworkType());
         assertTrue(mNetworkTypeController.areAnyTimersActive());
 
-        // switch to connected_rrc_idle before primary timer expires
+        // empty PCC, switch to connected_rrc_idle before primary timer expires
         physicalChannelConfigs.clear();
         mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
                 new AsyncResult(null, physicalChannelConfigs, null));
@@ -1498,16 +1571,40 @@
                 mNetworkTypeController.getOverrideNetworkType());
         assertTrue(mNetworkTypeController.areAnyTimersActive());
 
-        // 5 seconds passed during connected_mmwave -> connected_rrc_idle secondary timer
-        moveTimeForward(5 * 1000);
+        // Verify secondary timer is still active after 6 seconds passed during
+        // connected_mmwave -> connected_rrc_idle secondary timer, should still keep the primary
+        // state icon.
+        moveTimeForward((5 + 1) * 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);
+    @Test
+    public void testSecondaryTimerAdvanceBandReduceOnPciChange() throws Exception {
+        // The advance band secondary timer has been running for 6 seconds, 20 - 6 seconds are left.
+        testSecondaryTimerAdvanceBand();
+
+        // PCI changed from 1 to 2 for the first while the timer is running.
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, List.of(
+                        new PhysicalChannelConfig.Builder()
+                                .setPhysicalCellId(2)
+                                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                                .build()), null));
+        processAllMessages();
+
+        // Verify the first PCI change is exempted from triggering state change.
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // Verify the timer has been reduced from 20 - 6s(advance band) to 5s(regular).
+        moveTimeForward(5 * 1000);
         processAllMessages();
 
         assertEquals("connected_rrc_idle", getCurrentState().getName());
@@ -1695,22 +1792,6 @@
 
         // should trigger 10 second 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
-        physicalChannelConfigs.clear();
         mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
                 new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
@@ -1745,6 +1826,22 @@
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
                 mNetworkTypeController.getOverrideNetworkType());
         assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // primary cell changes again
+        physicalChannelConfigs.clear();
+        physicalChannelConfigs.add(new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(3)
+                .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_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
index 3fa4fd1..70e3d6b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
@@ -110,13 +110,18 @@
                     callingPkg, persistMessage, priority, expectMore, validityPeriod, messageId);
         }
 
-        public void testNotifySmsSentToEmergencyStateTracker(String destAddr, long messageId) {
-            notifySmsSentToEmergencyStateTracker(destAddr, messageId);
+        public void testNotifySmsSentToEmergencyStateTracker(String destAddr, long messageId,
+                boolean isOverIms) {
+            notifySmsSentToEmergencyStateTracker(destAddr, messageId, isOverIms);
         }
 
         public void testNotifySmsSentFailedToEmergencyStateTracker(String destAddr,
-                long messageId) {
-            notifySmsSentFailedToEmergencyStateTracker(destAddr, messageId);
+                long messageId, boolean isOverIms) {
+            notifySmsSentFailedToEmergencyStateTracker(destAddr, messageId, isOverIms);
+        }
+
+        public void testNotifySmsReceivedViaImsToEmergencyStateTracker(String origAddr) {
+            notifySmsReceivedViaImsToEmergencyStateTracker(origAddr);
         }
     }
 
@@ -498,15 +503,30 @@
 
     @Test
     @SmallTest
-    public void testNotifySmsSentToEmergencyStateTracker() throws Exception {
+    public void testNotifySmsSentToEmergencyStateTrackerOnDomainCs() throws Exception {
         setUpDomainSelectionEnabled(true);
         setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
 
-        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("911", 1L);
+        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("911", 1L, false);
         processAllMessages();
 
         verify(mTelephonyManager).isEmergencyNumber(eq("911"));
-        verify(mEmergencyStateTracker).endSms(eq("1"), eq(true));
+        verify(mEmergencyStateTracker)
+                .endSms(eq("1"), eq(true), eq(NetworkRegistrationInfo.DOMAIN_CS));
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifySmsSentToEmergencyStateTrackerOnDomainPs() throws Exception {
+        setUpDomainSelectionEnabled(true);
+        setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
+
+        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("911", 1L, true);
+        processAllMessages();
+
+        verify(mTelephonyManager).isEmergencyNumber(eq("911"));
+        verify(mEmergencyStateTracker)
+                .endSms(eq("1"), eq(true), eq(NetworkRegistrationInfo.DOMAIN_PS));
     }
 
     @Test
@@ -515,11 +535,11 @@
         setUpDomainSelectionEnabled(true);
         setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
 
-        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("1234", 1L);
+        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("1234", 1L, true);
         processAllMessages();
 
         verify(mTelephonyManager).isEmergencyNumber(eq("1234"));
-        verify(mEmergencyStateTracker, never()).endSms(anyString(), anyBoolean());
+        verify(mEmergencyStateTracker, never()).endSms(anyString(), anyBoolean(), anyInt());
     }
 
     @Test
@@ -527,22 +547,37 @@
     public void testNotifySmsSentToEmergencyStateTrackerWithoutEmergencyStateTracker()
             throws Exception {
         setUpDomainSelectionEnabled(true);
-        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("911", 1L);
+        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("911", 1L, true);
 
         verify(mTelephonyManager, never()).isEmergencyNumber(anyString());
     }
 
     @Test
     @SmallTest
-    public void testNotifySmsSentFailedToEmergencyStateTracker() throws Exception {
+    public void testNotifySmsSentFailedToEmergencyStateTrackerOnDomainCs() throws Exception {
         setUpDomainSelectionEnabled(true);
         setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
 
-        mSmsDispatchersController.testNotifySmsSentFailedToEmergencyStateTracker("911", 1L);
+        mSmsDispatchersController.testNotifySmsSentFailedToEmergencyStateTracker("911", 1L, false);
         processAllMessages();
 
         verify(mTelephonyManager).isEmergencyNumber(eq("911"));
-        verify(mEmergencyStateTracker).endSms(eq("1"), eq(false));
+        verify(mEmergencyStateTracker)
+                .endSms(eq("1"), eq(false), eq(NetworkRegistrationInfo.DOMAIN_CS));
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifySmsSentFailedToEmergencyStateTrackerOnDomainPs() throws Exception {
+        setUpDomainSelectionEnabled(true);
+        setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
+
+        mSmsDispatchersController.testNotifySmsSentFailedToEmergencyStateTracker("911", 1L, true);
+        processAllMessages();
+
+        verify(mTelephonyManager).isEmergencyNumber(eq("911"));
+        verify(mEmergencyStateTracker)
+                .endSms(eq("1"), eq(false), eq(NetworkRegistrationInfo.DOMAIN_PS));
     }
 
     @Test
@@ -552,11 +587,38 @@
         setUpDomainSelectionEnabled(true);
         setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
 
-        mSmsDispatchersController.testNotifySmsSentFailedToEmergencyStateTracker("1234", 1L);
+        mSmsDispatchersController.testNotifySmsSentFailedToEmergencyStateTracker("1234", 1L, true);
         processAllMessages();
 
         verify(mTelephonyManager).isEmergencyNumber(eq("1234"));
-        verify(mEmergencyStateTracker, never()).endSms(anyString(), anyBoolean());
+        verify(mEmergencyStateTracker, never()).endSms(anyString(), anyBoolean(), anyInt());
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifySmsReceivedViaImsToEmergencyStateTracker() throws Exception {
+        setUpDomainSelectionEnabled(true);
+        setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
+
+        mSmsDispatchersController.testNotifySmsReceivedViaImsToEmergencyStateTracker("911");
+        processAllMessages();
+
+        verify(mTelephonyManager).isEmergencyNumber(eq("911"));
+        verify(mEmergencyStateTracker).onEmergencySmsReceived();
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifySmsReceivedViaImsToEmergencyStateTrackerWithNonEmergencyNumber()
+            throws Exception {
+        setUpDomainSelectionEnabled(true);
+        setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
+
+        mSmsDispatchersController.testNotifySmsReceivedViaImsToEmergencyStateTracker("1234");
+        processAllMessages();
+
+        verify(mTelephonyManager).isEmergencyNumber(eq("1234"));
+        verify(mEmergencyStateTracker, never()).onEmergencySmsReceived();
     }
 
     @Test
@@ -781,7 +843,7 @@
                 mSmsDispatchersController, mEmergencyStateTracker);
         when(mEmergencyStateTracker.startEmergencySms(any(Phone.class), anyString(), anyBoolean()))
                 .thenReturn(mEmergencySmsFuture);
-        doNothing().when(mEmergencyStateTracker).endSms(anyString(), anyBoolean());
+        doNothing().when(mEmergencyStateTracker).endSms(anyString(), anyBoolean(), anyInt());
         mEmergencySmsFuture.complete(result);
         when(mTelephonyManager.isEmergencyNumber(eq("911"))).thenReturn(true);
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index a6e06e3..673acbc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -577,6 +577,8 @@
         mDomainSelectionResolver = Mockito.mock(DomainSelectionResolver.class);
         mNullCipherNotifier = Mockito.mock(NullCipherNotifier.class);
 
+        doReturn(true).when(mFeatureFlags).minimalTelephonyCdmCheck();
+
         TelephonyManager.disableServiceHandleCaching();
         PropertyInvalidatedCache.disableForTestMode();
         // For testing do not allow Log.WTF as it can cause test process to crash
@@ -640,7 +642,7 @@
                         nullable(CommandsInterface.class), nullable(FeatureFlags.class));
         doReturn(mEmergencyNumberTracker).when(mTelephonyComponentFactory)
                 .makeEmergencyNumberTracker(nullable(Phone.class),
-                        nullable(CommandsInterface.class));
+                        nullable(CommandsInterface.class), any(FeatureFlags.class));
         doReturn(getTestEmergencyNumber()).when(mEmergencyNumberTracker)
                 .getEmergencyNumber(any());
         doReturn(mUiccProfile).when(mTelephonyComponentFactory)
@@ -696,7 +698,7 @@
                 .makeIdentifierDisclosureNotifier(any(CellularNetworkSecuritySafetySource.class));
         doReturn(mNullCipherNotifier)
                 .when(mTelephonyComponentFactory)
-                .makeNullCipherNotifier();
+                .makeNullCipherNotifier(any(CellularNetworkSecuritySafetySource.class));
 
         //mPhone
         doReturn(mContext).when(mPhone).getContext();
@@ -821,6 +823,12 @@
         doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
         doReturn(true).when(mTelephonyManager).isDataCapable();
 
+        mContextFixture.addSystemFeature(PackageManager.FEATURE_TELECOM);
+        mContextFixture.addSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING);
+        mContextFixture.addSystemFeature(PackageManager.FEATURE_TELEPHONY_DATA);
+        mContextFixture.addSystemFeature(PackageManager.FEATURE_TELEPHONY_EUICC);
+        mContextFixture.addSystemFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING);
+
         doReturn(TelephonyManager.PHONE_TYPE_GSM).when(mTelephonyManager).getPhoneType();
         doReturn(mServiceState).when(mSST).getServiceState();
         doReturn(mServiceStateStats).when(mSST).getServiceStateStats();
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 50ee5c8..ddbe9c0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java
@@ -156,6 +156,7 @@
 
         // Change data config
         doReturn(true).when(mDataConfigManager).isPingTestBeforeAutoDataSwitchRequired();
+        doReturn(true).when(mDataConfigManager).doesAutoDataSwitchAllowRoaming();
         doReturn(10000L).when(mDataConfigManager)
                 .getAutoDataSwitchAvailabilityStabilityTimeThreshold();
         doReturn(120000L).when(mDataConfigManager)
@@ -184,7 +185,7 @@
         mEventsToAlarmListener = getPrivateField(mAutoDataSwitchControllerUT,
                 "mEventsToAlarmListener", Map.class);
 
-        doReturn(true).when(mFeatureFlags).autoSwitchAllowRoaming();
+        doReturn(true).when(mFeatureFlags).autoDataSwitchAllowRoaming();
         doReturn(true).when(mFeatureFlags).carrierEnabledSatelliteFlag();
     }
 
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 00e6680..9423551 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
@@ -872,7 +872,7 @@
         mMockSubInfo = Mockito.mock(SubscriptionInfo.class);
         mFeatureFlags = Mockito.mock(FeatureFlags.class);
         when(mTelephonyComponentFactory.makeDataSettingsManager(any(Phone.class),
-                any(DataNetworkController.class), any(Looper.class),
+                any(DataNetworkController.class), any(FeatureFlags.class), any(Looper.class),
                 any(DataSettingsManager.DataSettingsManagerCallback.class))).thenCallRealMethod();
         doReturn(mMockedImsMmTelManager).when(mMockedImsManager).getImsMmTelManager(anyInt());
         doReturn(mMockedImsRcsManager).when(mMockedImsManager).getImsRcsManager(anyInt());
@@ -1857,7 +1857,8 @@
         boolean isDataEnabled = mDataNetworkControllerUT.getDataSettingsManager().isDataEnabled();
         doReturn(mDataNetworkControllerUT.getDataSettingsManager())
                 .when(mPhone).getDataSettingsManager();
-        MultiSimSettingController controller = Mockito.spy(new MultiSimSettingController(mContext));
+        MultiSimSettingController controller = Mockito.spy(new MultiSimSettingController(mContext,
+                mFeatureFlags));
         doReturn(true).when(controller).isCarrierConfigLoadedForAllSub();
         replaceInstance(MultiSimSettingController.class, "sInstance", null, controller);
 
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 62fbebc..ebfc41a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -85,6 +86,7 @@
 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
 import com.android.internal.telephony.data.LinkBandwidthEstimator.LinkBandwidthEstimatorCallback;
 import com.android.internal.telephony.metrics.DataCallSessionStats;
+import com.android.internal.telephony.test.SimulatedCommands;
 
 import org.junit.After;
 import org.junit.Before;
@@ -949,7 +951,6 @@
                 eq(DataService.REQUEST_REASON_SHUTDOWN), any(Message.class));
     }
 
-
     @Test
     public void testCreateDataNetworkOnIwlan() throws Exception {
         doReturn(mIwlanNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
@@ -1171,6 +1172,59 @@
     }
 
     @Test
+    public void testNetworkRequestDetachedBeforePduSessionIdAllocated() throws Exception {
+        doReturn(mIwlanNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
+                eq(NetworkRegistrationInfo.DOMAIN_PS),
+                eq(AccessNetworkConstants.TRANSPORT_TYPE_WLAN));
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mAccessNetworksManager)
+                .getPreferredTransportByNetworkCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
+
+        NetworkRequestList networkRequestList = new NetworkRequestList();
+        TelephonyNetworkRequest networkRequest = new TelephonyNetworkRequest(
+                new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
+                .build(), mPhone);
+        networkRequestList.add(networkRequest);
+
+        SimulatedCommands simulatedCommands2 = mock(SimulatedCommands.class);
+        mPhone.mCi = simulatedCommands2;
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mImsDataProfile, networkRequestList,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN, DataAllowedReason.NORMAL,
+                mDataNetworkCallback);
+        replaceInstance(DataNetwork.class, "mDataCallSessionStats",
+                mDataNetworkUT, mDataCallSessionStats);
+        processAllMessages();
+        // Capture the message for allocatePduSessionId response. We want to delay it.
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(simulatedCommands2).allocatePduSessionId(messageCaptor.capture());
+        // Detach attached network request.
+        mDataNetworkUT.detachNetworkRequest(networkRequest, false);
+        processAllMessages();
+
+        // Pass response msg for the PDU session ID allocation.
+        Message msg = messageCaptor.getValue();
+        AsyncResult.forMessage(msg, 1, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        // Check setupDataCall was not called.
+        verify(mMockedWlanDataServiceManager, never()).setupDataCall(eq(AccessNetworkType.IWLAN),
+                eq(mImsDataProfile), eq(false), eq(false),
+                eq(DataService.REQUEST_REASON_NORMAL), nullable(LinkProperties.class),
+                eq(1), nullable(NetworkSliceInfo.class),
+                any(TrafficDescriptor.class), eq(true), any(Message.class));
+
+        // Check state changed to DISCONNECTED from CONNECTING
+        ArgumentCaptor<PreciseDataConnectionState> pdcsCaptor =
+                ArgumentCaptor.forClass(PreciseDataConnectionState.class);
+        verify(mPhone, times(2)).notifyDataConnection(pdcsCaptor.capture());
+        List<PreciseDataConnectionState> pdcsList = pdcsCaptor.getAllValues();
+        assertThat(pdcsList.get(0).getState()).isEqualTo(TelephonyManager.DATA_CONNECTING);
+        assertThat(pdcsList.get(1).getState()).isEqualTo(TelephonyManager.DATA_DISCONNECTED);
+    }
+
+    @Test
     public void testAdminAndOwnerUids() throws Exception {
         doReturn(ADMIN_UID2).when(mCarrierPrivilegesTracker).getCarrierServicePackageUid();
         setupDataNetwork();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java
index fc1bf0d..3f18a3a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java
@@ -77,7 +77,7 @@
                 .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
 
         mDataSettingsManagerUT = new DataSettingsManager(mPhone, mDataNetworkController,
-                Looper.myLooper(), mMockedDataSettingsManagerCallback);
+                mFeatureFlags, Looper.myLooper(), mMockedDataSettingsManagerCallback);
         logd("DataSettingsManagerTest -Setup!");
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java
index e508e5b..9f8b8ad 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java
@@ -58,6 +58,8 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class DataStallRecoveryManagerTest extends TelephonyTest {
+    private static final String KEY_IS_DSRS_DIAGNOSTICS_ENABLED =
+            "is_dsrs_diagnostics_enabled";
     private FakeContentResolver mFakeContentResolver;
 
     // Mocked classes
@@ -89,6 +91,7 @@
         Field field = DataStallRecoveryManager.class.getDeclaredField("mPredictWaitingMillis");
         field.setAccessible(true);
 
+        doReturn(true).when(mFeatureFlags).dsrsDiagnosticsEnabled();
         mFakeContentResolver = new FakeContentResolver();
         doReturn(mFakeContentResolver).when(mContext).getContentResolver();
         // Set the global settings for action enabled state and duration to
@@ -120,6 +123,7 @@
                         mPhone,
                         mDataNetworkController,
                         mMockedWwanDataServiceManager,
+                        mFeatureFlags,
                         mTestableLooper.getLooper(),
                         mDataStallRecoveryManagerCallback);
 
@@ -429,6 +433,7 @@
     @Test
     public void testSendDSRMData() throws Exception {
         ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+        boolean isDsrsDiagnosticsEnabled = mFeatureFlags.dsrsDiagnosticsEnabled();
 
         logd("Set phone status to normal status.");
         sendOnInternetDataNetworkCallback(true);
@@ -460,8 +465,13 @@
             logd(bundle.toString());
             int size = bundle.size();
             logd("bundle size is " + size);
-            // Check if bundle size is 19
-            assertThat(size).isEqualTo(19);
+            if (isDsrsDiagnosticsEnabled) {
+                // Check if bundle size is 27
+                assertThat(size).isEqualTo(27);
+            } else {
+                // Check if bundle size is 19
+                assertThat(size).isEqualTo(19);
+            }
         }
     }
 
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 f893c35..4a433c2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnectionTest.java
@@ -44,12 +44,14 @@
 import android.telephony.DomainSelectionService;
 import android.telephony.EmergencyRegistrationResult;
 import android.telephony.NetworkRegistrationInfo;
+import android.telephony.PreciseDisconnectCause;
 import android.telephony.data.ApnSetting;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.telephony.CallFailCause;
 import com.android.internal.telephony.ITransportSelectorCallback;
 import com.android.internal.telephony.ITransportSelectorResultCallback;
 import com.android.internal.telephony.IWwanSelectorCallback;
@@ -258,6 +260,39 @@
         verify(mAnm).unregisterForQualifiedNetworksChanged(any());
     }
 
+    @Test
+    @SmallTest
+    public void testGetSelectionAttributesEmergencyTempOrPermFailure() {
+        DomainSelectionService.SelectionAttributes attr =
+                EmergencyCallDomainSelectionConnection.getSelectionAttributes(
+                        mPhone.getPhoneId(), mPhone.getSubId(), false,
+                        TELECOM_CALL_ID1, "911", false,
+                        CallFailCause.IMS_EMERGENCY_TEMP_FAILURE, null, null);
+
+        assertEquals(PreciseDisconnectCause.EMERGENCY_TEMP_FAILURE, attr.getCsDisconnectCause());
+
+        attr = EmergencyCallDomainSelectionConnection.getSelectionAttributes(
+                        mPhone.getPhoneId(), mPhone.getSubId(), false,
+                        TELECOM_CALL_ID1, "911", false,
+                        CallFailCause.IMS_EMERGENCY_PERM_FAILURE, null, null);
+
+        assertEquals(PreciseDisconnectCause.EMERGENCY_PERM_FAILURE, attr.getCsDisconnectCause());
+
+        attr = EmergencyCallDomainSelectionConnection.getSelectionAttributes(
+                        mPhone.getPhoneId(), mPhone.getSubId(), false,
+                        TELECOM_CALL_ID1, "911", false,
+                        CallFailCause.EMERGENCY_TEMP_FAILURE, null, null);
+
+        assertEquals(PreciseDisconnectCause.EMERGENCY_TEMP_FAILURE, attr.getCsDisconnectCause());
+
+        attr = EmergencyCallDomainSelectionConnection.getSelectionAttributes(
+                        mPhone.getPhoneId(), mPhone.getSubId(), false,
+                        TELECOM_CALL_ID1, "911", false,
+                        CallFailCause.EMERGENCY_PERM_FAILURE, null, null);
+
+        assertEquals(PreciseDisconnectCause.EMERGENCY_PERM_FAILURE, attr.getCsDisconnectCause());
+    }
+
     private IWwanSelectorCallback onWwanSelected(ITransportSelectorCallback transportCallback)
             throws Exception {
         ITransportSelectorResultCallback cb = Mockito.mock(ITransportSelectorResultCallback.class);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
index c47eb3b..10dbfea 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
@@ -51,7 +51,6 @@
 
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.TelephonyTest;
 
 import com.google.i18n.phonenumbers.ShortNumberInfo;
@@ -61,6 +60,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
 
 
 import java.io.BufferedInputStream;
@@ -139,7 +139,7 @@
         mShortNumberInfo = mock(ShortNumberInfo.class);
         mCarrierConfigManagerMock = mock(CarrierConfigManager.class);
 
-        mContext = new ContextWrapper(InstrumentationRegistry.getTargetContext());
+        mContext = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
         mMockContext = mock(Context.class);
         mResources = mock(Resources.class);
 
@@ -151,9 +151,14 @@
         doReturn(1).when(mPhone2).getPhoneId();
         doReturn(SUB_ID_PHONE_2).when(mPhone2).getSubId();
 
+        doReturn(mPackageManager).when(mContext).getPackageManager();
+        doReturn(mPackageManager).when(mMockContext).getPackageManager();
+
         initializeEmergencyNumberListTestSamples();
-        mEmergencyNumberTrackerMock = new EmergencyNumberTracker(mPhone, mSimulatedCommands);
-        mEmergencyNumberTrackerMock2 = new EmergencyNumberTracker(mPhone2, mSimulatedCommands);
+        mEmergencyNumberTrackerMock = new EmergencyNumberTracker(mPhone, mSimulatedCommands,
+                mFeatureFlags);
+        mEmergencyNumberTrackerMock2 = new EmergencyNumberTracker(mPhone2, mSimulatedCommands,
+                mFeatureFlags);
         doReturn(mEmergencyNumberTrackerMock2).when(mPhone2).getEmergencyNumberTracker();
         mEmergencyNumberTrackerMock.DBG = true;
 
@@ -171,10 +176,13 @@
     public void tearDown() throws Exception {
         // Set back to single sim mode
         setSinglePhone();
-        Path target = Paths.get(mLocalDownloadDirectory.getPath(), EMERGENCY_NUMBER_DB_OTA_FILE);
-        Files.deleteIfExists(target);
-        mLocalDownloadDirectory.delete();
-        mLocalDownloadDirectory = null;
+        if (mLocalDownloadDirectory != null) {
+            Path target = Paths.get(mLocalDownloadDirectory.getPath(),
+                    EMERGENCY_NUMBER_DB_OTA_FILE);
+            Files.deleteIfExists(target);
+            mLocalDownloadDirectory.delete();
+            mLocalDownloadDirectory = null;
+        }
         mEmergencyNumberTrackerMock = null;
         mEmergencyNumberTrackerMock2 = null;
         mEmergencyNumberListTestSample.clear();
@@ -361,12 +369,12 @@
     @Test
     public void testRegistrationForCountryChangeIntent() throws Exception {
         EmergencyNumberTracker localEmergencyNumberTracker;
-        Context spyContext = spy(mContext);
-        doReturn(spyContext).when(mPhone).getContext();
+        Mockito.clearInvocations(mContext);
         ArgumentCaptor<IntentFilter> intentCaptor = ArgumentCaptor.forClass(IntentFilter.class);
 
-        localEmergencyNumberTracker = new EmergencyNumberTracker(mPhone, mSimulatedCommands);
-        verify(spyContext, times(1)).registerReceiver(any(), intentCaptor.capture());
+        localEmergencyNumberTracker = new EmergencyNumberTracker(mPhone, mSimulatedCommands,
+                mFeatureFlags);
+        verify(mContext, times(1)).registerReceiver(any(), intentCaptor.capture());
         IntentFilter ifilter = intentCaptor.getValue();
         assertTrue(ifilter.hasAction(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED));
     }
@@ -543,7 +551,7 @@
                 com.android.internal.R.bool.ignore_emergency_number_routing_from_db);
 
         EmergencyNumberTracker emergencyNumberTrackerMock = new EmergencyNumberTracker(
-                mPhone, mSimulatedCommands);
+                mPhone, mSimulatedCommands, mFeatureFlags);
         emergencyNumberTrackerMock.sendMessage(
                 emergencyNumberTrackerMock.obtainMessage(
                         1 /* EVENT_UNSOL_EMERGENCY_NUMBER_LIST */,
@@ -616,7 +624,7 @@
                 com.android.internal.R.bool.ignore_emergency_number_routing_from_db);
 
         EmergencyNumberTracker emergencyNumberTrackerMock = new EmergencyNumberTracker(
-                mPhone, mSimulatedCommands);
+                mPhone, mSimulatedCommands, mFeatureFlags);
         emergencyNumberTrackerMock.sendMessage(
                 emergencyNumberTrackerMock.obtainMessage(
                         1 /* EVENT_UNSOL_EMERGENCY_NUMBER_LIST */,
@@ -720,7 +728,7 @@
                 com.android.internal.R.bool.ignore_emergency_number_routing_from_db);
 
         EmergencyNumberTracker emergencyNumberTrackerMock = new EmergencyNumberTracker(
-                mPhone, mSimulatedCommands);
+                mPhone, mSimulatedCommands, mFeatureFlags);
         emergencyNumberTrackerMock.sendMessage(
                 emergencyNumberTrackerMock.obtainMessage(
                         1 /* EVENT_UNSOL_EMERGENCY_NUMBER_LIST */,
@@ -843,7 +851,7 @@
         ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
         EmergencyNumberTracker localEmergencyNumberTracker =
-                new EmergencyNumberTracker(mPhone, mSimulatedCommands);
+                new EmergencyNumberTracker(mPhone, mSimulatedCommands, mFeatureFlags);
         verify(mCarrierConfigManagerMock)
                 .registerCarrierConfigChangeListener(any(), listenerArgumentCaptor.capture());
         CarrierConfigManager.CarrierConfigChangeListener carrierConfigChangeListener =
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 105f2bf..3eb7659 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
@@ -17,7 +17,9 @@
 package com.android.internal.telephony.emergency;
 
 import static android.telephony.AccessNetworkConstants.AccessNetworkType.EUTRAN;
+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.REGISTRATION_STATE_HOME;
 
 import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_CALLBACK;
@@ -60,7 +62,6 @@
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -95,7 +96,7 @@
 public class EmergencyStateTrackerTest extends TelephonyTest {
     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 int TEST_ECM_EXIT_TIMEOUT_MS = 500;
     private static final EmergencyRegistrationResult E_REG_RESULT = new EmergencyRegistrationResult(
             EUTRAN, REGISTRATION_STATE_HOME, DOMAIN_CS_PS, true, true, 0, 1, "001", "01", "US");
 
@@ -772,216 +773,6 @@
     }
 
     /**
-     * Test that once endCall() for IMS call is called and we enter ECM, then we exit ECM
-     * after the specified timeout.
-     */
-    @Test
-    @SmallTest
-    public void endCall_entersEcm_thenExitsEcmAfterTimeoutImsCall() throws Exception {
-        // Setup EmergencyStateTracker
-        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
-                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
-        // Create test Phone
-        Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
-                /* isRadioOn= */ true);
-        setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT);
-        setUpAsyncResultForExitEmergencyMode(testPhone);
-        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                mTestConnection1, false);
-        // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
-        emergencyStateTracker.onEmergencyCallDomainUpdated(
-                PhoneConstants.PHONE_TYPE_IMS, mTestConnection1);
-        // Set ecm as supported
-        setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
-
-        processAllMessages();
-
-        emergencyStateTracker.endCall(mTestConnection1);
-
-        assertTrue(emergencyStateTracker.isInEcm());
-
-        Context mockContext = mock(Context.class);
-        replaceInstance(EmergencyStateTracker.class, "mContext",
-                emergencyStateTracker, mockContext);
-        processAllFutureMessages();
-
-        ArgumentCaptor<TelephonyCallback> callbackCaptor =
-                ArgumentCaptor.forClass(TelephonyCallback.class);
-
-        verify(mTelephonyManagerProxy).registerTelephonyCallback(eq(testPhone.getSubId()),
-                  any(), callbackCaptor.capture());
-
-        TelephonyCallback callback = callbackCaptor.getValue();
-
-        assertNotNull(callback);
-
-        // Verify exitEmergencyMode() is called after timeout
-        verify(testPhone).exitEmergencyMode(any(Message.class));
-        assertFalse(emergencyStateTracker.isInEmergencyMode());
-        assertFalse(emergencyStateTracker.isInEcm());
-        verify(mTelephonyManagerProxy).unregisterTelephonyCallback(eq(callback));
-    }
-
-    /**
-     * Test that startEmergencyCall() is called right after exiting ECM on the same slot.
-     */
-    @Test
-    @SmallTest
-    public void exitEcm_thenDialEmergencyCallOnTheSameSlotRightAfter() throws Exception {
-        // Setup EmergencyStateTracker
-        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
-                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
-        emergencyStateTracker.setPdnDisconnectionTimeoutMs(0);
-        // Create test Phone
-        Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
-                /* isRadioOn= */ true);
-        setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT);
-        setUpAsyncResultForExitEmergencyMode(testPhone);
-
-        verify(testPhone, times(0)).setEmergencyMode(anyInt(), any(Message.class));
-        verify(testPhone, times(0)).exitEmergencyMode(any(Message.class));
-
-        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                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, mTestConnection1);
-        emergencyStateTracker.onEmergencyCallDomainUpdated(
-                PhoneConstants.PHONE_TYPE_IMS, mTestConnection1);
-        // Set ecm as supported
-        setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
-
-        processAllMessages();
-
-        emergencyStateTracker.endCall(mTestConnection1);
-
-        assertTrue(emergencyStateTracker.isInEcm());
-
-        Context mockContext = mock(Context.class);
-        replaceInstance(EmergencyStateTracker.class, "mContext",
-                emergencyStateTracker, mockContext);
-        processAllFutureMessages();
-
-        ArgumentCaptor<TelephonyCallback> callbackCaptor =
-                ArgumentCaptor.forClass(TelephonyCallback.class);
-
-        verify(mTelephonyManagerProxy).registerTelephonyCallback(eq(testPhone.getSubId()),
-                  any(), callbackCaptor.capture());
-
-        TelephonyCallback callback = callbackCaptor.getValue();
-
-        assertNotNull(callback);
-        assertFalse(emergencyStateTracker.isInEmergencyMode());
-        assertFalse(emergencyStateTracker.isInEcm());
-        verify(mTelephonyManagerProxy, times(0)).unregisterTelephonyCallback(eq(callback));
-        verify(testPhone, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
-        verify(testPhone, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK),
-                any(Message.class));
-        verify(testPhone, times(0)).exitEmergencyMode(any(Message.class));
-
-        replaceInstance(EmergencyStateTracker.class, "mContext", emergencyStateTracker, mContext);
-
-        // dial on the same slot
-        unused = emergencyStateTracker.startEmergencyCall(testPhone, mTestConnection1, false);
-        processAllMessages();
-
-        assertTrue(emergencyStateTracker.isInEmergencyMode());
-        assertFalse(emergencyStateTracker.isInEcm());
-        verify(mTelephonyManagerProxy, times(1)).unregisterTelephonyCallback(eq(callback));
-        verify(testPhone, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
-        verify(testPhone, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK),
-                any(Message.class));
-        verify(testPhone, times(0)).exitEmergencyMode(any(Message.class));
-    }
-
-    /**
-     * Test that startEmergencyCall() is called right after exiting ECM on the other slot.
-     */
-    @Test
-    @SmallTest
-    public void exitEcm_thenDialEmergencyCallOnTheOtherSlotRightAfter() throws Exception {
-        // Setup EmergencyStateTracker
-        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
-                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
-        emergencyStateTracker.setPdnDisconnectionTimeoutMs(0);
-        // Create test Phone
-        Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
-                /* isRadioOn= */ true);
-        setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT);
-        setUpAsyncResultForExitEmergencyMode(testPhone);
-
-        verify(testPhone, times(0)).setEmergencyMode(anyInt(), any(Message.class));
-        verify(testPhone, times(0)).exitEmergencyMode(any(Message.class));
-
-        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                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, mTestConnection1);
-        emergencyStateTracker.onEmergencyCallDomainUpdated(
-                PhoneConstants.PHONE_TYPE_IMS, mTestConnection1);
-        // Set ecm as supported
-        setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
-
-        processAllMessages();
-
-        emergencyStateTracker.endCall(mTestConnection1);
-
-        assertTrue(emergencyStateTracker.isInEcm());
-
-        Context mockContext = mock(Context.class);
-        replaceInstance(EmergencyStateTracker.class, "mContext",
-                emergencyStateTracker, mockContext);
-        processAllFutureMessages();
-
-        ArgumentCaptor<TelephonyCallback> callbackCaptor =
-                ArgumentCaptor.forClass(TelephonyCallback.class);
-
-        verify(mTelephonyManagerProxy).registerTelephonyCallback(eq(testPhone.getSubId()),
-                  any(), callbackCaptor.capture());
-
-        TelephonyCallback callback = callbackCaptor.getValue();
-
-        assertNotNull(callback);
-        assertFalse(emergencyStateTracker.isInEmergencyMode());
-        assertFalse(emergencyStateTracker.isInEcm());
-        verify(mTelephonyManagerProxy, times(0)).unregisterTelephonyCallback(eq(callback));
-        verify(testPhone, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
-        verify(testPhone, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK),
-                any(Message.class));
-        verify(testPhone, times(0)).exitEmergencyMode(any(Message.class));
-
-        Phone phone1 = getPhone(1);
-        verify(phone1, times(0)).setEmergencyMode(anyInt(), any(Message.class));
-        verify(phone1, times(0)).exitEmergencyMode(any(Message.class));
-
-        replaceInstance(EmergencyStateTracker.class, "mContext", emergencyStateTracker, mContext);
-
-        // dial on the other slot
-        unused = emergencyStateTracker.startEmergencyCall(phone1, mTestConnection1, false);
-        processAllMessages();
-
-        assertTrue(emergencyStateTracker.isInEmergencyMode());
-        assertFalse(emergencyStateTracker.isInEcm());
-        verify(mTelephonyManagerProxy, times(1)).unregisterTelephonyCallback(eq(callback));
-        verify(testPhone, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
-        verify(testPhone, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK),
-                any(Message.class));
-        verify(testPhone, times(1)).exitEmergencyMode(any(Message.class));
-        verify(phone1, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
-        verify(phone1, times(0)).exitEmergencyMode(any(Message.class));
-    }
-
-    /**
      * Test that once endCall() is called and we enter ECM, then we exit ECM when turning on
      * airplane mode.
      */
@@ -1160,14 +951,16 @@
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEcm());
+        verify(phone0, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        verify(phone0, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
 
         // Second emergency call started.
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
                 mTestConnection2, false);
 
-        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
-        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
-                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        assertFalse(future.isDone());
+        verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        verify(phone0, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
     }
 
     @Test
@@ -1230,9 +1023,7 @@
         future = emergencyStateTracker.startEmergencyCall(phone0, mTestConnection2, false);
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
-        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
-        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
-                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        assertFalse(future.isDone());
 
         emergencyStateTracker.onEmergencyTransportChanged(
                 EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WLAN);
@@ -1297,7 +1088,7 @@
 
     @Test
     @SmallTest
-    public void testEndSms() {
+    public void testEndSmsAndExitEmergencyMode() {
         EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                 /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
         Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false,
@@ -1315,10 +1106,105 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, true);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_CS);
 
         verify(phone0).exitEmergencyMode(any(Message.class));
         assertFalse(emergencyStateTracker.isInEmergencyMode());
+        // CS domain doesn't support SCBM.
+        assertFalse(emergencyStateTracker.isInScbm());
+    }
+
+    @Test
+    @SmallTest
+    public void testEndSmsAndEnterEmergencySmsCallbackMode() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        assertTrue(emergencyStateTracker.getEmergencyRegistrationResult().equals(E_REG_RESULT));
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+
+        verify(mCarrierConfigManager).getConfigForSubId(anyInt(),
+                eq(CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT));
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInScbm());
+    }
+
+    @Test
+    @SmallTest
+    public void testEndSmsWithSmsSentFailureWhileInScbm() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false,
+                /* isRadioOn= */ true);
+        setUpScbm(phone0, emergencyStateTracker);
+        processAllMessages();
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID_2, false);
+        processAllMessages();
+
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        assertTrue(emergencyStateTracker.isInScbm());
+
+        // Set emergency transport
+        emergencyStateTracker.onEmergencyTransportChanged(
+                EmergencyStateTracker.EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN);
+
+        // When MO SMS fails while in SCBM.
+        emergencyStateTracker.endSms(TEST_SMS_ID_2, false, DOMAIN_PS);
+
+        verify(mCarrierConfigManager).getConfigForSubId(anyInt(),
+                eq(CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT));
+        verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInScbm());
+    }
+
+    @Test
+    @SmallTest
+    public void testEndSmsWithSmsSentSuccessWhileInScbm() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false,
+                /* isRadioOn= */ true);
+        setUpScbm(phone0, emergencyStateTracker);
+        processAllMessages();
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID_2, false);
+        processAllMessages();
+
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        assertTrue(emergencyStateTracker.isInScbm());
+
+        // Set emergency transport
+        emergencyStateTracker.onEmergencyTransportChanged(
+                EmergencyStateTracker.EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN);
+
+        // When MO SMS is successfully sent while in SCBM.
+        emergencyStateTracker.endSms(TEST_SMS_ID_2, true, DOMAIN_PS);
+
+        verify(mCarrierConfigManager, times(2)).getConfigForSubId(anyInt(),
+                eq(CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT));
+        verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInScbm());
     }
 
     @Test
@@ -1449,6 +1335,24 @@
 
     @Test
     @SmallTest
+    public void testStartEmergencySmsWhileInScbm() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false,
+                /* isRadioOn= */ true);
+        setUpScbm(phone0, emergencyStateTracker);
+
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID_2, false);
+
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        verify(phone0, never()).exitEmergencyMode(any(Message.class));
+    }
+
+    @Test
+    @SmallTest
     public void testStartEmergencySmsUsingDifferentPhone() {
         EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                 /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
@@ -1470,6 +1374,36 @@
 
     @Test
     @SmallTest
+    public void testStartEmergencySmsWhileInScbmOnDifferentPhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForExitEmergencyMode(phone0);
+        setUpScbm(phone0, emergencyStateTracker);
+        processAllMessages();
+
+        Phone phone1 = getPhone(1);
+        setUpAsyncResultForSetEmergencyMode(phone1, E_REG_RESULT);
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone1,
+                TEST_SMS_ID_2, false);
+
+        verify(phone0).exitEmergencyMode(any(Message.class));
+        // Waits for exiting emergency mode on other phone.
+        assertFalse(future.isDone());
+
+        processAllMessages();
+
+        verify(phone1).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        processAllMessages();
+
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+    }
+
+    @Test
+    @SmallTest
     public void testStartEmergencyCallActiveAndSmsOnSamePhone() {
         EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                 /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
@@ -1532,6 +1466,185 @@
 
     @Test
     @SmallTest
+    public void testStartEmergencyCallWhileCallActiveAndInScbmOnSamePhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        // Emergency call is in active.
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection1, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        // Expect: DisconnectCause#NOT_DISCONNECTED
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+
+        // Emergency SMS is successfully sent and SCBM entered.
+        setUpScbm(phone0, emergencyStateTracker);
+        processAllMessages();
+
+        future = emergencyStateTracker.startEmergencyCall(phone0, mTestConnection2, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        // Ensure that SCBM is exited.
+        assertFalse(emergencyStateTracker.isInScbm());
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencyCallWhileInEcmAndScbmOnSamePhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        setEcmSupportedConfig(phone0, true);
+        // Emergency call is in active.
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection1, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        // Expect: DisconnectCause#NOT_DISCONNECTED
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+        emergencyStateTracker.endCall(mTestConnection1);
+
+        assertTrue(emergencyStateTracker.isInEcm());
+
+        // Emergency SMS is successfully sent and SCBM entered.
+        setUpScbm(phone0, emergencyStateTracker);
+        processAllMessages();
+
+        future = emergencyStateTracker.startEmergencyCall(phone0, mTestConnection2, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEcm());
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        // Ensure that SCBM is exited.
+        assertFalse(emergencyStateTracker.isInScbm());
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencyCallWhileScbmInProgressOnSamePhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        // Emergency SMS is successfully sent and SCBM entered.
+        setUpScbm(phone0, emergencyStateTracker);
+
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        // Emergency call is started.
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection1, false);
+        processAllMessages();
+
+        verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        // Ensure that SCBM is exited.
+        assertFalse(emergencyStateTracker.isInScbm());
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencyCallWhileInScbmOnSamePhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        // Emergency SMS is successfully sent and SCBM entered.
+        setUpScbm(phone0, emergencyStateTracker);
+        processAllMessages();
+
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        // Emergency call is started.
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection1, false);
+        processAllMessages();
+
+        verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        // Ensure that SCBM is exited.
+        assertFalse(emergencyStateTracker.isInScbm());
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencyCallAndEnterScbmAndEndCallOnSamePhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        setUpAsyncResultForExitEmergencyMode(phone0);
+        setEcmSupportedConfig(phone0, true);
+        // Emergency call is in active.
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection1, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+
+        // Emergency SMS is being started.
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, false);
+
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        assertFalse(emergencyStateTracker.isInScbm());
+
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+
+        assertTrue(emergencyStateTracker.isInScbm());
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+
+        setEcmSupportedConfig(phone0, false);
+        emergencyStateTracker.endCall(mTestConnection1);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertFalse(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+        assertTrue(emergencyStateTracker.isInScbm());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+    }
+
+    @Test
+    @SmallTest
     public void testStartEmergencySmsActiveAndCallOnSamePhone() {
         EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                 /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
@@ -1553,8 +1666,7 @@
         future = emergencyStateTracker.startEmergencyCall(phone0, mTestConnection1, false);
         processAllMessages();
 
-        verify(phone0).exitEmergencyMode(any(Message.class));
-        verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         // Expect: DisconnectCause#NOT_DISCONNECTED.
@@ -1615,6 +1727,48 @@
 
     @Test
     @SmallTest
+    public void testStartEmergencySmsInProgressAndStartEmergencyCallAndEndSmsOnSamePhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForExitEmergencyMode(phone0);
+        // Emergency SMS is in progress.
+        CompletableFuture<Integer> smsFuture = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        ArgumentCaptor<Message> smsCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), smsCaptor.capture());
+
+        // Emergency call is being started.
+        CompletableFuture<Integer> callFuture = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection1, false);
+
+        assertFalse(smsFuture.isDone());
+        assertFalse(callFuture.isDone());
+
+        emergencyStateTracker.endSms(TEST_SMS_ID, false, DOMAIN_PS);
+
+        // Response message for setEmergencyMode by SMS.
+        Message msg = smsCaptor.getValue();
+        AsyncResult.forMessage(msg, E_REG_RESULT, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        // Response message for setEmergencyMode by call.
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        processAllMessages();
+
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(callFuture.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+    }
+
+    @Test
+    @SmallTest
     public void testStartEmergencyCallAndSmsOnDifferentPhone() {
         EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                 /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
@@ -1731,6 +1885,73 @@
 
     @Test
     @SmallTest
+    public void testStartEmergencyCallWhileScbmInProgressOnDifferentPhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForExitEmergencyMode(phone0);
+        // Emergency SMS is successfully sent and SCBM entered.
+        setUpScbm(phone0, emergencyStateTracker);
+
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        // Emergency call is being started using the different phone.
+        Phone phone1 = getPhone(1);
+        setUpAsyncResultForSetEmergencyMode(phone1, E_REG_RESULT);
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone1,
+                mTestConnection1, false);
+        processAllMessages();
+
+        // Exit emergency mode and set the emergency mode again by the call when the exit result
+        // is received for obtaining the latest EmergencyRegResult.
+        verify(phone0).exitEmergencyMode(any(Message.class));
+        verify(phone1).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        // Ensure that SCBM is exited.
+        assertFalse(emergencyStateTracker.isInScbm());
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencyCallWhileInScbmOnDifferentPhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForExitEmergencyMode(phone0);
+        // Emergency SMS is successfully sent and SCBM entered.
+        setUpScbm(phone0, emergencyStateTracker);
+        processAllMessages();
+
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        // Emergency call is being started using the different phone.
+        Phone phone1 = getPhone(1);
+        setUpAsyncResultForSetEmergencyMode(phone1, E_REG_RESULT);
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone1,
+                mTestConnection1, false);
+        processAllMessages();
+
+        // Exit emergency mode and set the emergency mode again by the call when the exit result
+        // is received for obtaining the latest EmergencyRegResult.
+        verify(phone0).exitEmergencyMode(any(Message.class));
+        verify(phone1).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        // Ensure that SCBM is exited.
+        assertFalse(emergencyStateTracker.isInScbm());
+    }
+
+    @Test
+    @SmallTest
     public void testExitEmergencyModeCallAndSmsOnSamePhone() {
         EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                 /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
@@ -1762,7 +1983,7 @@
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, true);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
         processAllMessages();
 
         assertFalse(emergencyStateTracker.isInEmergencyMode());
@@ -1799,7 +2020,7 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, true);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
 
@@ -1846,7 +2067,7 @@
         assertTrue(emergencyStateTracker.isInEcm());
         assertFalse(emergencyStateTracker.isInEmergencyCall());
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, true);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
         processAllMessages();
 
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
@@ -1901,7 +2122,7 @@
 
         emergencyStateTracker.onEmergencyTransportChanged(
                 EmergencyStateTracker.EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN);
-        emergencyStateTracker.endSms(TEST_SMS_ID, true);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
         processAllMessages();
 
         // Enter emergency callback mode and emergency mode changed by SMS end.
@@ -1909,13 +2130,15 @@
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEcm());
         assertFalse(emergencyStateTracker.isInEmergencyCall());
+        assertTrue(emergencyStateTracker.isInScbm());
 
-        // ECM timeout.
+        // ECM/SCBM timeout.
         processAllFutureMessages();
 
         assertFalse(emergencyStateTracker.isInEmergencyMode());
         assertFalse(emergencyStateTracker.isInEcm());
         assertFalse(emergencyStateTracker.isInEmergencyCall());
+        assertFalse(emergencyStateTracker.isInScbm());
         verify(phone0).exitEmergencyMode(any(Message.class));
     }
 
@@ -1948,7 +2171,7 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, true);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
 
@@ -1970,6 +2193,130 @@
 
     @Test
     @SmallTest
+    public void testExitEmergencyModeWhenExitingEcmAndScbm() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        setUpAsyncResultForExitEmergencyMode(phone0);
+        setEcmSupportedConfig(phone0, true);
+        // Emergency call is in active.
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection1, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+
+        // Emergency SMS is being started.
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, false);
+
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        emergencyStateTracker.endCall(mTestConnection1);
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        setScbmTimerValue(phone0, TEST_ECM_EXIT_TIMEOUT_MS + 100);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+        processAllMessages();
+
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+        assertTrue(emergencyStateTracker.isInScbm());
+
+        // ECM timeout.
+        moveTimeForward(TEST_ECM_EXIT_TIMEOUT_MS + 50);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertFalse(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+        assertTrue(emergencyStateTracker.isInScbm());
+
+        // SCBM timeout.
+        processAllFutureMessages();
+
+        assertFalse(emergencyStateTracker.isInEmergencyMode());
+        assertFalse(emergencyStateTracker.isInScbm());
+        verify(phone0).exitEmergencyMode(any(Message.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testExitEmergencyModeWhenExitingScbmAndEcm() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        setUpAsyncResultForExitEmergencyMode(phone0);
+        setEcmSupportedConfig(phone0, true);
+        // Emergency call is in active.
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection1, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+
+        // Emergency SMS is being started.
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, false);
+
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        emergencyStateTracker.endCall(mTestConnection1);
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        setScbmTimerValue(phone0, TEST_ECM_EXIT_TIMEOUT_MS - 100);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+        processAllMessages();
+
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+        assertTrue(emergencyStateTracker.isInScbm());
+
+        // SCBM timeout.
+        moveTimeForward(TEST_ECM_EXIT_TIMEOUT_MS - 50);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+        assertFalse(emergencyStateTracker.isInScbm());
+
+        // ECM timeout.
+        processAllFutureMessages();
+
+        assertFalse(emergencyStateTracker.isInEmergencyMode());
+        assertFalse(emergencyStateTracker.isInEcm());
+        verify(phone0).exitEmergencyMode(any(Message.class));
+    }
+
+    @Test
+    @SmallTest
     public void testSaveKeyEmergencyCallbackModeSupportedBool() {
         mContextFixture.getCarrierConfigBundle().putBoolean(
                 CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
@@ -1995,14 +2342,14 @@
                 listenerArgumentCaptor.getAllValues().get(0);
 
         // Verify carrier config for valid subscription
-        assertTrue(testEst.isEmergencyCallbackModeSupported());
+        assertTrue(testEst.isEmergencyCallbackModeSupported(phone));
 
         // SIM removed
         when(phone.getSubId()).thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         setEcmSupportedConfig(phone, false);
 
         // Verify default config for invalid subscription
-        assertFalse(testEst.isEmergencyCallbackModeSupported());
+        assertFalse(testEst.isEmergencyCallbackModeSupported(phone));
 
         // Insert SIM again
         when(phone.getSubId()).thenReturn(1);
@@ -2023,7 +2370,7 @@
                 TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
 
         // Verify saved config for valid subscription
-        assertTrue(testEst.isEmergencyCallbackModeSupported());
+        assertTrue(testEst.isEmergencyCallbackModeSupported(phone));
 
         // Insert SIM again, but emergency callback mode not supported
         when(phone.getSubId()).thenReturn(1);
@@ -2035,7 +2382,7 @@
                 TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
 
         // Verify carrier config for valid subscription
-        assertFalse(testEst.isEmergencyCallbackModeSupported());
+        assertFalse(testEst.isEmergencyCallbackModeSupported(phone));
 
         // SIM removed again
         when(phone.getSubId()).thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
@@ -2047,7 +2394,7 @@
                 TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
 
         // Verify saved config for valid subscription
-        assertFalse(testEst.isEmergencyCallbackModeSupported());
+        assertFalse(testEst.isEmergencyCallbackModeSupported(phone));
     }
 
     /**
@@ -2263,24 +2610,20 @@
     }
 
     private void setEcmSupportedConfig(Phone phone, boolean ecmSupported) {
-        CarrierConfigManager cfgManager = (CarrierConfigManager) mContext
-                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        cfgManager.getConfigForSubId(phone.getSubId()).putBoolean(
+        mCarrierConfigManager.getConfigForSubId(phone.getSubId()).putBoolean(
                 CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL,
                 ecmSupported);
     }
 
     private void setConfigForDdsSwitch(Phone phone, String[] roaminPlmns,
             int suplEmergencyModeType, String esExtensionSec) {
-        CarrierConfigManager cfgManager = (CarrierConfigManager) mContext
-                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        cfgManager.getConfigForSubId(phone.getSubId()).putStringArray(
+        mCarrierConfigManager.getConfigForSubId(phone.getSubId()).putStringArray(
                 CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
                 roaminPlmns);
-        cfgManager.getConfigForSubId(phone.getSubId()).putInt(
+        mCarrierConfigManager.getConfigForSubId(phone.getSubId()).putInt(
                 CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
                 suplEmergencyModeType);
-        cfgManager.getConfigForSubId(phone.getSubId())
+        mCarrierConfigManager.getConfigForSubId(phone.getSubId())
                 .putString(CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, esExtensionSec);
     }
 
@@ -2304,4 +2647,36 @@
             return null;
         }).when(phone).exitEmergencyMode(any(Message.class));
     }
+
+    private void setUpScbm(Phone phone, EmergencyStateTracker emergencyStateTracker) {
+        setUpAsyncResultForSetEmergencyMode(phone, E_REG_RESULT);
+        setEcmSupportedConfig(phone, true);
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone,
+                TEST_SMS_ID, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        verify(phone).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        assertTrue(emergencyStateTracker.getEmergencyRegistrationResult().equals(E_REG_RESULT));
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        // Expect: entering SCBM.
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+
+        verify(mCarrierConfigManager).getConfigForSubId(anyInt(),
+                eq(CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT));
+        if (!emergencyStateTracker.isInEcm() && !emergencyStateTracker.isInEmergencyCall()) {
+            verify(phone).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+        }
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInScbm());
+    }
+
+    private void setScbmTimerValue(Phone phone, int millis) {
+        mCarrierConfigManager.getConfigForSubId(phone.getSubId()).putInt(
+                CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT, millis);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
index 79b1ada..6056112 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
@@ -2497,6 +2497,34 @@
     }
 
     @Test
+    @SmallTest
+    public void testDomainSelectionEmergencyPermFailure() {
+        doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported();
+        startOutgoingCall();
+        ImsPhoneConnection c = mCTUT.mForegroundCall.getFirstConnection();
+        mImsCallListener.onCallStartFailed(mSecondImsCall,
+                new ImsReasonInfo(ImsReasonInfo.CODE_EMERGENCY_PERM_FAILURE, -1));
+        processAllMessages();
+        assertNotNull(c.getImsReasonInfo());
+        assertEquals(ImsReasonInfo.CODE_EMERGENCY_PERM_FAILURE,
+                c.getImsReasonInfo().getCode());
+    }
+
+    @Test
+    @SmallTest
+    public void testDomainSelectionEmergencyTempFailure() {
+        doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported();
+        startOutgoingCall();
+        ImsPhoneConnection c = mCTUT.mForegroundCall.getFirstConnection();
+        mImsCallListener.onCallStartFailed(mSecondImsCall,
+                new ImsReasonInfo(ImsReasonInfo.CODE_EMERGENCY_TEMP_FAILURE, -1));
+        processAllMessages();
+        assertNotNull(c.getImsReasonInfo());
+        assertEquals(ImsReasonInfo.CODE_EMERGENCY_TEMP_FAILURE,
+                c.getImsReasonInfo().getCode());
+    }
+
+    @Test
     public void testUpdateImsCallStatusIncoming() throws Exception {
         // Incoming call
         ImsPhoneConnection connection = setupRingingConnection();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/CellularSecurityTransparencyStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/CellularSecurityTransparencyStatsTest.java
new file mode 100644
index 0000000..2149de0
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/CellularSecurityTransparencyStatsTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.metrics;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.telephony.CellularIdentifierDisclosure;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class CellularSecurityTransparencyStatsTest {
+
+    private CellularSecurityTransparencyStats mCellularSecurityStats;
+
+    @Before
+    public void setUp() throws Exception {
+        mCellularSecurityStats = spy(new CellularSecurityTransparencyStats());
+    }
+
+    @Test
+    public void testLogIdentifierDisclosure_NullSimPlmn() {
+        CellularIdentifierDisclosure disclosure = new CellularIdentifierDisclosure(
+                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
+                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI, "123-456", true);
+
+        mCellularSecurityStats.logIdentifierDisclosure(disclosure, null, null, true);
+
+        verify(mCellularSecurityStats).writeIdentifierDisclosure(-1, -1, 123, 456,
+                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI,
+                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST, true,
+                true);
+    }
+
+    @Test
+    public void testLogIdentifierDisclosure_badSimPlmn() {
+        CellularIdentifierDisclosure disclosure = new CellularIdentifierDisclosure(
+                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
+                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI, "123-456", true);
+
+        mCellularSecurityStats.logIdentifierDisclosure(disclosure, "INCORRECTLY", "FORMATTED",
+                true);
+
+        verify(mCellularSecurityStats).writeIdentifierDisclosure(-1, -1, 123, 456,
+                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI,
+                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST, true,
+                true);
+    }
+
+    @Test
+    public void testLogIdentifierDisclosure_badDisclosurePlmn() {
+        CellularIdentifierDisclosure disclosure = new CellularIdentifierDisclosure(
+                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
+                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI, "INCORRECT", true);
+
+        mCellularSecurityStats.logIdentifierDisclosure(disclosure, "123", "456", true);
+
+        verify(mCellularSecurityStats).writeIdentifierDisclosure(123, 456, -1, -1,
+                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI,
+                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST, true,
+                true);
+    }
+
+    @Test
+    public void testLogIdentifierDisclosure_expectedGoodData() {
+        CellularIdentifierDisclosure disclosure = new CellularIdentifierDisclosure(
+                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
+                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI, "999-666", true);
+
+        mCellularSecurityStats.logIdentifierDisclosure(disclosure, "123", "456", true);
+
+        verify(mCellularSecurityStats).writeIdentifierDisclosure(123, 456, 999, 666,
+                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI,
+                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST, true,
+                true);
+
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java
index 0426737..35f1bb7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java
@@ -70,8 +70,8 @@
                     .setCoolDownMillis(24L * 3600L * 1000L)
                     .build();
     private static final long MIN_COOLDOWN_MILLIS = 23L * 3600L * 1000L;
-    private static final long CELL_SERVICE_MIN_COOLDOWN_MILLIS =
-            IS_DEBUGGABLE ? 4L *  60L * 1000L : MIN_COOLDOWN_MILLIS;
+    private static final long POWER_CORRELATED_MIN_COOLDOWN_MILLIS =
+            IS_DEBUGGABLE ? 4L *  60L * 1000L : 5L * 3600L * 1000L;
     private static final long MIN_CALLS_PER_BUCKET = 5L;
 
     // NOTE: these fields are currently 32-bit internally and padded to 64-bit by TelephonyManager
@@ -402,6 +402,9 @@
     @SmallTest
     public void onPullAtom_cellularServiceState_tooFrequent() throws Exception {
         doReturn(null).when(mPersistAtomsStorage).getCellularServiceStates(anyLong());
+        mContextFixture.putIntResource(
+                com.android.internal.R.integer.config_metrics_pull_cooldown_millis,
+                (int) POWER_CORRELATED_MIN_COOLDOWN_MILLIS);
         List<StatsEvent> actualAtoms = new ArrayList<>();
 
         int result = mMetricsCollector.onPullAtom(CELLULAR_SERVICE_STATE, actualAtoms);
@@ -409,7 +412,7 @@
         assertThat(actualAtoms).hasSize(0);
         assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
         verify(mPersistAtomsStorage, times(1)).getCellularServiceStates(
-                eq(CELL_SERVICE_MIN_COOLDOWN_MILLIS));
+                eq(POWER_CORRELATED_MIN_COOLDOWN_MILLIS));
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
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 f1a8de9..aae6a2f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
@@ -1736,6 +1736,8 @@
 
         // Trigger carrier config changed with carrierEnabledSatelliteFlag enabled
         when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
         for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
                 : mCarrierConfigChangedListenerList) {
             pair.first.execute(() -> pair.second.onCarrierConfigChanged(
@@ -2723,8 +2725,8 @@
         verify(mMockSatelliteModemInterface, times(1)).setSatellitePlmn(anyInt(),
                 eq(plmnListPerCarrier), eq(allSatellitePlmnList), any(Message.class));
 
-        // If the PlmnListPerCarrier and the overlay config plmn list are exist verify passing
-        // the modem.
+        // If the PlmnListPerCarrier and the overlay config plmn list are exist but
+        // KEY_SATELLITE_ATTACH_SUPPORTED_BOOL is false, verify passing to the modem.
         entitlementPlmnList = Arrays.stream(new String[]{"00101", "00102", "00103"}).toList();
         overlayConfigPlmnList =
                 Arrays.stream(new String[]{"00101", "00102", "00104"}).toList();
@@ -2735,6 +2737,22 @@
                 entitlementPlmnList, mIIntegerConsumer);
 
         plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID);
+        assertEquals(new ArrayList<>(), plmnListPerCarrier);
+        allSatellitePlmnList = SatelliteServiceUtils.mergeStrLists(
+                entitlementPlmnList, overlayConfigPlmnList);
+        verify(mMockSatelliteModemInterface, times(1)).setSatellitePlmn(anyInt(),
+                eq(entitlementPlmnList), eq(allSatellitePlmnList), any(Message.class));
+
+        // If the PlmnListPerCarrier and the overlay config plmn list are exist and
+        // KEY_SATELLITE_ATTACH_SUPPORTED_BOOL is true verify passing the modem.
+        reset(mMockSatelliteModemInterface);
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
+
+        mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, true,
+                entitlementPlmnList, mIIntegerConsumer);
+
+        plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID);
         allSatellitePlmnList = SatelliteServiceUtils.mergeStrLists(
                 plmnListPerCarrier, overlayConfigPlmnList);
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
index 20b33eb..d12828a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
@@ -514,6 +514,24 @@
         assertEquals(0, testSOSMessageRecommender.getCountOfTimerStarted());
     }
 
+    @Test
+    public void testIsSatelliteViaOemAvailable() {
+        Boolean originalIsSatelliteViaOemProvisioned =
+                mTestSatelliteController.mIsSatelliteViaOemProvisioned;
+
+        mTestSatelliteController.mIsSatelliteViaOemProvisioned = null;
+        assertFalse(mTestSOSMessageRecommender.isSatelliteViaOemAvailable());
+
+        mTestSatelliteController.mIsSatelliteViaOemProvisioned = true;
+        assertTrue(mTestSOSMessageRecommender.isSatelliteViaOemAvailable());
+
+        mTestSatelliteController.mIsSatelliteViaOemProvisioned = false;
+        assertFalse(mTestSOSMessageRecommender.isSatelliteViaOemAvailable());
+
+        mTestSatelliteController.mIsSatelliteViaOemProvisioned =
+                originalIsSatelliteViaOemProvisioned;
+    }
+
     private void testStopTrackingCallBeforeTimeout(
             @Connection.ConnectionState int connectionState) {
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
@@ -614,7 +632,7 @@
                 mProvisionStateChangedCallbacks;
         private int mRegisterForSatelliteProvisionStateChangedCalls = 0;
         private int mUnregisterForSatelliteProvisionStateChangedCalls = 0;
-        private boolean mIsSatelliteViaOemProvisioned = true;
+        private Boolean mIsSatelliteViaOemProvisioned = true;
         private boolean mIsSatelliteConnectedViaCarrierWithinHysteresisTime = true;
         public boolean isOemEnabledSatelliteSupported = true;
         public boolean isCarrierEnabledSatelliteSupported = true;
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 8841c7a..aba8164 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifierTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifierTest.java
@@ -29,12 +29,17 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.telephony.CellularIdentifierDisclosure;
 
 import com.android.internal.telephony.TestExecutorService;
+import com.android.internal.telephony.metrics.CellularSecurityTransparencyStats;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.InOrder;
@@ -50,6 +55,9 @@
     private static final int SUB_ID_2 = 2;
     private CellularIdentifierDisclosure mDislosure;
     private CellularNetworkSecuritySafetySource mSafetySource;
+    private CellularSecurityTransparencyStats mStats;
+    private TestExecutorService mExecutor;
+    private SubscriptionManagerService mSubscriptionManagerService;
     private Context mContext;
     private InOrder mInOrder;
 
@@ -62,15 +70,16 @@
                         "001001",
                         false);
         mSafetySource = mock(CellularNetworkSecuritySafetySource.class);
+        mStats = mock(CellularSecurityTransparencyStats.class);
+        mExecutor = new TestExecutorService();
+        mSubscriptionManagerService = mock(SubscriptionManagerService.class);
+        mContext = mock(Context.class);
         mInOrder = inOrder(mSafetySource);
     }
 
     @Test
     public void testInitializeDisabled() {
-        TestExecutorService executor = new TestExecutorService();
-        CellularIdentifierDisclosureNotifier notifier =
-                new CellularIdentifierDisclosureNotifier(
-                        executor, 15, TimeUnit.MINUTES, mSafetySource);
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
 
         assertFalse(notifier.isEnabled());
         verify(mSafetySource, never()).setIdentifierDisclosureIssueEnabled(any(), anyBoolean());
@@ -78,10 +87,7 @@
 
     @Test
     public void testDisableAddDisclosureNop() {
-        TestExecutorService executor = new TestExecutorService();
-        CellularIdentifierDisclosureNotifier notifier =
-                new CellularIdentifierDisclosureNotifier(
-                        executor, 15, TimeUnit.MINUTES, mSafetySource);
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
 
         assertFalse(notifier.isEnabled());
         notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
@@ -92,10 +98,8 @@
 
     @Test
     public void testAddDisclosureEmergencyNop() {
-        TestExecutorService executor = new TestExecutorService();
-        CellularIdentifierDisclosureNotifier notifier =
-                new CellularIdentifierDisclosureNotifier(
-                        executor, 15, TimeUnit.MINUTES, mSafetySource);
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
+
         CellularIdentifierDisclosure emergencyDisclosure =
                 new CellularIdentifierDisclosure(
                         CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
@@ -113,11 +117,7 @@
 
     @Test
     public void testAddDisclosureCountIncrements() {
-        TestExecutorService executor = new TestExecutorService();
-        CellularIdentifierDisclosureNotifier notifier =
-                new CellularIdentifierDisclosureNotifier(
-                        executor, 15, TimeUnit.MINUTES, mSafetySource);
-
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
         notifier.enable(mContext);
 
         for (int i = 0; i < 3; i++) {
@@ -135,28 +135,20 @@
 
     @Test
     public void testSingleDisclosureStartAndEndTimesAreEqual() {
-        TestExecutorService executor = new TestExecutorService();
-        CellularIdentifierDisclosureNotifier notifier =
-                new CellularIdentifierDisclosureNotifier(
-                        executor, 15, TimeUnit.MINUTES, mSafetySource);
-
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
         notifier.enable(mContext);
 
         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)));
+        Assert.assertEquals(notifier.getFirstOpen(SUB_ID_1), 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, mSafetySource);
-
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
         notifier.enable(mContext);
 
         notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
@@ -175,10 +167,7 @@
 
     @Test
     public void testAddDisclosureThenWindowClose() {
-        TestExecutorService executor = new TestExecutorService();
-        CellularIdentifierDisclosureNotifier notifier =
-                new CellularIdentifierDisclosureNotifier(
-                        executor, 15, TimeUnit.MINUTES, mSafetySource);
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
 
         // One round of disclosures
         notifier.enable(mContext);
@@ -191,7 +180,7 @@
                 .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(2), any(), any());
 
         // Window close should reset the counter
-        executor.advanceTime(WINDOW_CLOSE_ADVANCE_MILLIS);
+        mExecutor.advanceTime(WINDOW_CLOSE_ADVANCE_MILLIS);
         assertEquals(0, notifier.getCurrentDisclosureCount(SUB_ID_1));
 
         // A new disclosure should increment as normal
@@ -203,10 +192,7 @@
 
     @Test
     public void testDisableClosesWindow() {
-        TestExecutorService executor = new TestExecutorService();
-        CellularIdentifierDisclosureNotifier notifier =
-                new CellularIdentifierDisclosureNotifier(
-                        executor, 15, TimeUnit.MINUTES, mSafetySource);
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
 
         // One round of disclosures
         notifier.enable(mContext);
@@ -231,10 +217,7 @@
 
     @Test
     public void testMultipleSubIdsTrackedIndependently() {
-        TestExecutorService executor = new TestExecutorService();
-        CellularIdentifierDisclosureNotifier notifier =
-                new CellularIdentifierDisclosureNotifier(
-                        executor, 15, TimeUnit.MINUTES, mSafetySource);
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
 
         notifier.enable(mContext);
         for (int i = 0; i < 3; i++) {
@@ -262,4 +245,27 @@
         assertEquals(3, notifier.getCurrentDisclosureCount(SUB_ID_1));
         assertEquals(4, notifier.getCurrentDisclosureCount(SUB_ID_2));
     }
+
+    @Test
+    public void testLogDisclsoure() {
+        String mcc = "100";
+        String mnc = "200";
+
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
+        SubscriptionInfoInternal subInfoMock = mock(SubscriptionInfoInternal.class);
+        when(mSubscriptionManagerService.getSubscriptionInfoInternal(SUB_ID_1)).thenReturn(
+                subInfoMock);
+        when(subInfoMock.getMcc()).thenReturn(mcc);
+        when(subInfoMock.getMnc()).thenReturn(mnc);
+
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
+
+        verify(mStats, times(1)).logIdentifierDisclosure(mDislosure, mcc, mnc,
+                mDislosure.isEmergency());
+    }
+
+    private CellularIdentifierDisclosureNotifier getNotifier() {
+        return new CellularIdentifierDisclosureNotifier(mExecutor, 15, TimeUnit.MINUTES,
+                mSafetySource, mSubscriptionManagerService, mStats);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/security/NullCipherNotifierTest.java b/tests/telephonytests/src/com/android/internal/telephony/security/NullCipherNotifierTest.java
new file mode 100644
index 0000000..0bb7b76
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/security/NullCipherNotifierTest.java
@@ -0,0 +1,377 @@
+/*
+ * 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.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.eq;
+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.SecurityAlgorithmUpdate;
+
+import com.android.internal.telephony.TestExecutorService;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+public class NullCipherNotifierTest {
+
+    private static final int SUB_ID = 3425;
+    private static final List<Integer> NON_TRANSPORT_LAYER_EVENTS =
+            List.of(SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_SIP,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_SIP_SOS,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_RTP,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_RTP_SOS,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_SIP,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_SIP_SOS,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_RTP,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_RTP_SOS);
+    private static final List<Integer> TRANSPORT_LAYER_EVENTS =
+            List.of(SecurityAlgorithmUpdate.CONNECTION_EVENT_CS_SIGNALLING_GSM,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_PS_SIGNALLING_GPRS,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_CS_SIGNALLING_3G,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_PS_SIGNALLING_3G,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_NAS_SIGNALLING_LTE,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_LTE,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_NAS_SIGNALLING_5G,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_5G);
+    private static final List<Integer> NULL_CIPHERS =
+            List.of(SecurityAlgorithmUpdate.SECURITY_ALGORITHM_A50,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_GEA0,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_UEA0,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA0,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_NEA0,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_IMS_NULL,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SIP_NULL,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SRTP_NULL,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_OTHER);
+    private static final List<Integer> NON_NULL_CIPHERS =
+            List.of(SecurityAlgorithmUpdate.SECURITY_ALGORITHM_A51,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_A52,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_A53,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_A54,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_GEA1,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_GEA2,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_GEA3,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_GEA4,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_GEA5,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_UEA1,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_UEA2,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA1,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA3,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_NEA1,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_NEA2,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_NEA3,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SIP_NO_IPSEC_CONFIG,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_AES_GCM,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_AES_GMAC,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_AES_CBC,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_DES_EDE3_CBC,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_AES_EDE3_CBC,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_MD5_96,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_RTP,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SRTP_AES_COUNTER,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SRTP_AES_F8,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SRTP_HMAC_SHA1,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_ENCR_AES_GCM_16,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_ENCR_AES_CBC,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_ORYX);
+
+    private CellularNetworkSecuritySafetySource mSafetySource;
+    private Context mContext;
+    private TestExecutorService mExecutor = new TestExecutorService();
+
+    @Before
+    public void setUp() {
+        mSafetySource = mock(CellularNetworkSecuritySafetySource.class);
+    }
+
+    @Test
+    public void initializeNotifier_notifierAndSafetySourceDisabled() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+
+        assertThat(notifier.isEnabled()).isFalse();
+        verify(mSafetySource, never()).setNullCipherIssueEnabled(any(), anyBoolean());
+    }
+
+    @Test
+    public void enable_enablesSafetySource() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+
+        notifier.enable(mContext);
+
+        assertThat(notifier.isEnabled()).isTrue();
+        verify(mSafetySource, times(1)).setNullCipherIssueEnabled(eq(mContext), eq(true));
+    }
+
+    @Test
+    public void disable_disablesSafetySource() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
+
+        notifier.disable(mContext);
+
+        assertThat(notifier.isEnabled()).isFalse();
+        verify(mSafetySource, times(1)).setNullCipherIssueEnabled(eq(mContext), eq(false));
+    }
+
+    @Test
+    public void onSecurityAlgorithmUpdate_enabled_unprotectedEmergency_noSafetySourceUpdate() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+
+        notifier.onSecurityAlgorithmUpdate(
+                mContext,
+                SUB_ID,
+                new SecurityAlgorithmUpdate(
+                        SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_5G,
+                        SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2,
+                        SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96,
+                        /* isUnprotectedEmergency= */ true));
+
+        verify(mSafetySource, never()).setNullCipherState(any(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void onSecurityAlgorithmUpdate_enabled_nonTransportLayerEvent_noSafetySourceUpdate() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+
+        for (int connectionEvent : NON_TRANSPORT_LAYER_EVENTS) {
+            clearInvocations(mSafetySource);
+            notifier.onSecurityAlgorithmUpdate(
+                    mContext,
+                    SUB_ID,
+                    new SecurityAlgorithmUpdate(
+                            connectionEvent,
+                            SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2,
+                            SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96,
+                            /* isUnprotectedEmergency= */ false));
+
+            verify(mSafetySource, never().description("Connection event: " + connectionEvent))
+                    .setNullCipherState(any(), anyInt(), anyInt());
+        }
+    }
+
+    @Test
+    public void onUpdate_enabled_transportLayerEvent_encryptionNullCipher_notifyNonEncrypted() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+
+        for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
+            for (int encryptionAlgorithm : NULL_CIPHERS) {
+                clearInvocations(mSafetySource);
+                notifier.onSecurityAlgorithmUpdate(
+                        mContext,
+                        SUB_ID,
+                        new SecurityAlgorithmUpdate(
+                                connectionEvent,
+                                encryptionAlgorithm,
+                                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96,
+                                /* isUnprotectedEmergency= */ false));
+
+                verify(
+                        mSafetySource,
+                        times(1).description(
+                                "Connection event: " + connectionEvent
+                                        + " Encryption algorithm: " + encryptionAlgorithm))
+                        .setNullCipherState(
+                                eq(mContext),
+                                eq(SUB_ID),
+                                eq(NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED));
+            }
+        }
+    }
+
+    @Test
+    public void onUpdate_enabled_transportLayerEvent_integrityNullCipher_notifyNonEncrypted() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+
+        for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
+            for (int integrityAlgorithm : NULL_CIPHERS) {
+                clearInvocations(mSafetySource);
+                notifier.onSecurityAlgorithmUpdate(
+                        mContext,
+                        SUB_ID,
+                        new SecurityAlgorithmUpdate(
+                                connectionEvent,
+                                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2,
+                                integrityAlgorithm,
+                                /* isUnprotectedEmergency= */ false));
+
+                verify(
+                        mSafetySource,
+                        times(1).description(
+                                "Connection event: " + connectionEvent
+                                        + " Integrity algorithm: " + integrityAlgorithm))
+                        .setNullCipherState(
+                                eq(mContext),
+                                eq(SUB_ID),
+                                eq(NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED));
+            }
+        }
+    }
+
+    @Test
+    public void onUpdate_enabled_transportLayerEvent_encryptionNonNullCipher_encrypted() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+
+        for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
+            for (int encryptionAlgorithm : NON_NULL_CIPHERS) {
+                clearInvocations(mSafetySource);
+                notifier.onSecurityAlgorithmUpdate(
+                        mContext,
+                        SUB_ID,
+                        new SecurityAlgorithmUpdate(
+                                connectionEvent,
+                                encryptionAlgorithm,
+                                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96,
+                                /* isUnprotectedEmergency= */ false));
+
+                verify(
+                        mSafetySource,
+                        times(1).description(
+                                "Connection event: " + connectionEvent
+                                        + " Encryption algorithm: " + encryptionAlgorithm))
+                        .setNullCipherState(
+                                eq(mContext),
+                                eq(SUB_ID),
+                                eq(NULL_CIPHER_STATE_ENCRYPTED));
+            }
+        }
+    }
+
+    @Test
+    public void onUpdate_enabled_transportLayerEvent_integrityNonNullCipher_encrypted() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+
+        for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
+            for (int integrityAlgorithm : NON_NULL_CIPHERS) {
+                clearInvocations(mSafetySource);
+                notifier.onSecurityAlgorithmUpdate(
+                        mContext,
+                        SUB_ID,
+                        new SecurityAlgorithmUpdate(
+                                connectionEvent,
+                                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2,
+                                integrityAlgorithm,
+                                /* isUnprotectedEmergency= */ false));
+
+                verify(
+                        mSafetySource,
+                        times(1).description(
+                                "Connection event: " + connectionEvent
+                                        + " Integrity algorithm: " + integrityAlgorithm))
+                        .setNullCipherState(
+                                eq(mContext),
+                                eq(SUB_ID),
+                                eq(NULL_CIPHER_STATE_ENCRYPTED));
+            }
+        }
+    }
+
+    @Test
+    public void onUpdate_enabled_transportLayerEvent_encryptionNonNullCipher_notifyEncrypted() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+
+        for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
+            for (int encryptionAlgorithm : NON_NULL_CIPHERS) {
+                notifier.onSecurityAlgorithmUpdate(
+                        mContext,
+                        SUB_ID,
+                        new SecurityAlgorithmUpdate(
+                                connectionEvent,
+                                NULL_CIPHERS.get(0),
+                                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96,
+                                /* isUnprotectedEmergency= */ false));
+
+                clearInvocations(mSafetySource);
+                notifier.onSecurityAlgorithmUpdate(
+                        mContext,
+                        SUB_ID,
+                        new SecurityAlgorithmUpdate(
+                                connectionEvent,
+                                encryptionAlgorithm,
+                                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96,
+                                /* isUnprotectedEmergency= */ false));
+
+                verify(
+                        mSafetySource,
+                        times(1).description(
+                                "Connection event: " + connectionEvent
+                                        + " Encryption algorithm: " + encryptionAlgorithm))
+                        .setNullCipherState(
+                                eq(mContext),
+                                eq(SUB_ID),
+                                eq(NULL_CIPHER_STATE_NOTIFY_ENCRYPTED));
+            }
+        }
+    }
+
+    @Test
+    public void onUpdate_enabled_transportLayerEvent_integrityNonNullCipher_notifyEncrypted() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
+
+        for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
+            for (int integrityAlgorithm : NON_NULL_CIPHERS) {
+                notifier.onSecurityAlgorithmUpdate(
+                        mContext,
+                        SUB_ID,
+                        new SecurityAlgorithmUpdate(
+                                connectionEvent,
+                                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2,
+                                NULL_CIPHERS.get(0),
+                                /* isUnprotectedEmergency= */ false));
+
+                clearInvocations(mSafetySource);
+                notifier.onSecurityAlgorithmUpdate(
+                        mContext,
+                        SUB_ID,
+                        new SecurityAlgorithmUpdate(
+                                connectionEvent,
+                                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2,
+                                integrityAlgorithm,
+                                /* isUnprotectedEmergency= */ false));
+
+                verify(
+                        mSafetySource,
+                        times(1).description(
+                                "Connection event: " + connectionEvent
+                                        + " Integrity algorithm: " + integrityAlgorithm))
+                        .setNullCipherState(
+                                eq(mContext),
+                                eq(SUB_ID),
+                                eq(NULL_CIPHER_STATE_NOTIFY_ENCRYPTED));
+            }
+        }
+    }
+}